From ea346a785dc1b3f7c156f6fc33da634e1f1a627b Mon Sep 17 00:00:00 2001 From: Chris Schlaeger Date: Tue, 12 Aug 2014 21:56:44 +0200 Subject: Adding jquery, flot and openlayers to be included with the GEM. --- misc/openlayers/OpenLayers.mobile.debug.js | 41831 +++++++++++++++++++++++++++ 1 file changed, 41831 insertions(+) create mode 100644 misc/openlayers/OpenLayers.mobile.debug.js (limited to 'misc/openlayers/OpenLayers.mobile.debug.js') diff --git a/misc/openlayers/OpenLayers.mobile.debug.js b/misc/openlayers/OpenLayers.mobile.debug.js new file mode 100644 index 0000000..5cb37cd --- /dev/null +++ b/misc/openlayers/OpenLayers.mobile.debug.js @@ -0,0 +1,41831 @@ +/* + + OpenLayers.js -- OpenLayers Map Viewer Library + + Copyright (c) 2006-2013 by OpenLayers Contributors + Published under the 2-clause BSD license. + See http://openlayers.org/dev/license.txt for the full text of the license, and http://openlayers.org/dev/authors.txt for full list of contributors. + + Includes compressed code under the following licenses: + + (For uncompressed versions of the code used, please see the + OpenLayers Github repository: ) + +*/ + +/** + * Contains XMLHttpRequest.js + * Copyright 2007 Sergey Ilinsky (http://www.ilinsky.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +/** + * OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is + * Copyright (c) 2006, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* ====================================================================== + OpenLayers/SingleFile.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +var OpenLayers = { + /** + * Constant: VERSION_NUMBER + */ + VERSION_NUMBER: "Release 2.13.1", + + /** + * Constant: singleFile + * TODO: remove this in 3.0 when we stop supporting build profiles that + * include OpenLayers.js + */ + singleFile: true, + + /** + * Method: _getScriptLocation + * Return the path to this script. This is also implemented in + * OpenLayers.js + * + * Returns: + * {String} Path to this script + */ + _getScriptLocation: (function() { + var r = new RegExp("(^|(.*?\\/))(OpenLayers[^\\/]*?\\.js)(\\?|$)"), + s = document.getElementsByTagName('script'), + src, m, l = ""; + for(var i=0, len=s.length; i + * + * (end code) + * + * Please remember that when your OpenLayers script is not named + * "OpenLayers.js" you will have to make sure that the default theme is + * loaded into the page by including an appropriate -tag, + * e.g.: + * + * (code) + * + * (end code) + */ + ImgPath : '' +}; +/* ====================================================================== + OpenLayers/BaseTypes.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/SingleFile.js + */ + +/** + * Header: OpenLayers Base Types + * OpenLayers custom string, number and function functions are described here. + */ + +/** + * Namespace: OpenLayers.String + * Contains convenience functions for string manipulation. + */ +OpenLayers.String = { + + /** + * APIFunction: startsWith + * Test whether a string starts with another string. + * + * Parameters: + * str - {String} The string to test. + * sub - {String} The substring to look for. + * + * Returns: + * {Boolean} The first string starts with the second. + */ + startsWith: function(str, sub) { + return (str.indexOf(sub) == 0); + }, + + /** + * APIFunction: contains + * Test whether a string contains another string. + * + * Parameters: + * str - {String} The string to test. + * sub - {String} The substring to look for. + * + * Returns: + * {Boolean} The first string contains the second. + */ + contains: function(str, sub) { + return (str.indexOf(sub) != -1); + }, + + /** + * APIFunction: trim + * Removes leading and trailing whitespace characters from a string. + * + * Parameters: + * str - {String} The (potentially) space padded string. This string is not + * modified. + * + * Returns: + * {String} A trimmed version of the string with all leading and + * trailing spaces removed. + */ + trim: function(str) { + return str.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); + }, + + /** + * APIFunction: camelize + * Camel-case a hyphenated string. + * Ex. "chicken-head" becomes "chickenHead", and + * "-chicken-head" becomes "ChickenHead". + * + * Parameters: + * str - {String} The string to be camelized. The original is not modified. + * + * Returns: + * {String} The string, camelized + */ + camelize: function(str) { + var oStringList = str.split('-'); + var camelizedString = oStringList[0]; + for (var i=1, len=oStringList.length; i replacement = context[a]; + // 1 -> replacement = context[a][b]; + // 2 -> replacement = context[a][b][c]; + var subs = match.split(/\.+/); + for (var i=0; i< subs.length; i++) { + if (i == 0) { + replacement = context; + } + if (replacement === undefined) { + break; + } + replacement = replacement[subs[i]]; + } + + if(typeof replacement == "function") { + replacement = args ? + replacement.apply(null, args) : + replacement(); + } + + // If replacement is undefined, return the string 'undefined'. + // This is a workaround for a bugs in browsers not properly + // dealing with non-participating groups in regular expressions: + // http://blog.stevenlevithan.com/archives/npcg-javascript + if (typeof replacement == 'undefined') { + return 'undefined'; + } else { + return replacement; + } + }; + + return template.replace(OpenLayers.String.tokenRegEx, replacer); + }, + + /** + * Property: tokenRegEx + * Used to find tokens in a string. + * Examples: ${a}, ${a.b.c}, ${a-b}, ${5} + */ + tokenRegEx: /\$\{([\w.]+?)\}/g, + + /** + * Property: numberRegEx + * Used to test strings as numbers. + */ + numberRegEx: /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/, + + /** + * APIFunction: isNumeric + * Determine whether a string contains only a numeric value. + * + * Examples: + * (code) + * OpenLayers.String.isNumeric("6.02e23") // true + * OpenLayers.String.isNumeric("12 dozen") // false + * OpenLayers.String.isNumeric("4") // true + * OpenLayers.String.isNumeric(" 4 ") // false + * (end) + * + * Returns: + * {Boolean} String contains only a number. + */ + isNumeric: function(value) { + return OpenLayers.String.numberRegEx.test(value); + }, + + /** + * APIFunction: numericIf + * Converts a string that appears to be a numeric value into a number. + * + * Parameters: + * value - {String} + * trimWhitespace - {Boolean} + * + * Returns: + * {Number|String} a Number if the passed value is a number, a String + * otherwise. + */ + numericIf: function(value, trimWhitespace) { + var originalValue = value; + if (trimWhitespace === true && value != null && value.replace) { + value = value.replace(/^\s*|\s*$/g, ""); + } + return OpenLayers.String.isNumeric(value) ? parseFloat(value) : originalValue; + } + +}; + +/** + * Namespace: OpenLayers.Number + * Contains convenience functions for manipulating numbers. + */ +OpenLayers.Number = { + + /** + * Property: decimalSeparator + * Decimal separator to use when formatting numbers. + */ + decimalSeparator: ".", + + /** + * Property: thousandsSeparator + * Thousands separator to use when formatting numbers. + */ + thousandsSeparator: ",", + + /** + * APIFunction: limitSigDigs + * Limit the number of significant digits on a float. + * + * Parameters: + * num - {Float} + * sig - {Integer} + * + * Returns: + * {Float} The number, rounded to the specified number of significant + * digits. + */ + limitSigDigs: function(num, sig) { + var fig = 0; + if (sig > 0) { + fig = parseFloat(num.toPrecision(sig)); + } + return fig; + }, + + /** + * APIFunction: format + * Formats a number for output. + * + * Parameters: + * num - {Float} + * dec - {Integer} Number of decimal places to round to. + * Defaults to 0. Set to null to leave decimal places unchanged. + * tsep - {String} Thousands separator. + * Default is ",". + * dsep - {String} Decimal separator. + * Default is ".". + * + * Returns: + * {String} A string representing the formatted number. + */ + format: function(num, dec, tsep, dsep) { + dec = (typeof dec != "undefined") ? dec : 0; + tsep = (typeof tsep != "undefined") ? tsep : + OpenLayers.Number.thousandsSeparator; + dsep = (typeof dsep != "undefined") ? dsep : + OpenLayers.Number.decimalSeparator; + + if (dec != null) { + num = parseFloat(num.toFixed(dec)); + } + + var parts = num.toString().split("."); + if (parts.length == 1 && dec == null) { + // integer where we do not want to touch the decimals + dec = 0; + } + + var integer = parts[0]; + if (tsep) { + var thousands = /(-?[0-9]+)([0-9]{3})/; + while(thousands.test(integer)) { + integer = integer.replace(thousands, "$1" + tsep + "$2"); + } + } + + var str; + if (dec == 0) { + str = integer; + } else { + var rem = parts.length > 1 ? parts[1] : "0"; + if (dec != null) { + rem = rem + new Array(dec - rem.length + 1).join("0"); + } + str = integer + dsep + rem; + } + return str; + }, + + /** + * Method: zeroPad + * Create a zero padded string optionally with a radix for casting numbers. + * + * Parameters: + * num - {Number} The number to be zero padded. + * len - {Number} The length of the string to be returned. + * radix - {Number} An integer between 2 and 36 specifying the base to use + * for representing numeric values. + */ + zeroPad: function(num, len, radix) { + var str = num.toString(radix || 10); + while (str.length < len) { + str = "0" + str; + } + return str; + } +}; + +/** + * Namespace: OpenLayers.Function + * Contains convenience functions for function manipulation. + */ +OpenLayers.Function = { + /** + * APIFunction: bind + * Bind a function to an object. Method to easily create closures with + * 'this' altered. + * + * Parameters: + * func - {Function} Input function. + * object - {Object} The object to bind to the input function (as this). + * + * Returns: + * {Function} A closure with 'this' set to the passed in object. + */ + bind: function(func, object) { + // create a reference to all arguments past the second one + var args = Array.prototype.slice.apply(arguments, [2]); + return function() { + // Push on any additional arguments from the actual function call. + // These will come after those sent to the bind call. + var newArgs = args.concat( + Array.prototype.slice.apply(arguments, [0]) + ); + return func.apply(object, newArgs); + }; + }, + + /** + * APIFunction: bindAsEventListener + * Bind a function to an object, and configure it to receive the event + * object as first parameter when called. + * + * Parameters: + * func - {Function} Input function to serve as an event listener. + * object - {Object} A reference to this. + * + * Returns: + * {Function} + */ + bindAsEventListener: function(func, object) { + return function(event) { + return func.call(object, event || window.event); + }; + }, + + /** + * APIFunction: False + * A simple function to that just does "return false". We use this to + * avoid attaching anonymous functions to DOM event handlers, which + * causes "issues" on IE<8. + * + * Usage: + * document.onclick = OpenLayers.Function.False; + * + * Returns: + * {Boolean} + */ + False : function() { + return false; + }, + + /** + * APIFunction: True + * A simple function to that just does "return true". We use this to + * avoid attaching anonymous functions to DOM event handlers, which + * causes "issues" on IE<8. + * + * Usage: + * document.onclick = OpenLayers.Function.True; + * + * Returns: + * {Boolean} + */ + True : function() { + return true; + }, + + /** + * APIFunction: Void + * A reusable function that returns ``undefined``. + * + * Returns: + * {undefined} + */ + Void: function() {} + +}; + +/** + * Namespace: OpenLayers.Array + * Contains convenience functions for array manipulation. + */ +OpenLayers.Array = { + + /** + * APIMethod: filter + * Filter an array. Provides the functionality of the + * Array.prototype.filter extension to the ECMA-262 standard. Where + * available, Array.prototype.filter will be used. + * + * Based on well known example from http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/filter + * + * Parameters: + * array - {Array} The array to be filtered. This array is not mutated. + * Elements added to this array by the callback will not be visited. + * callback - {Function} A function that is called for each element in + * the array. If this function returns true, the element will be + * included in the return. The function will be called with three + * arguments: the element in the array, the index of that element, and + * the array itself. If the optional caller parameter is specified + * the callback will be called with this set to caller. + * caller - {Object} Optional object to be set as this when the callback + * is called. + * + * Returns: + * {Array} An array of elements from the passed in array for which the + * callback returns true. + */ + filter: function(array, callback, caller) { + var selected = []; + if (Array.prototype.filter) { + selected = array.filter(callback, caller); + } else { + var len = array.length; + if (typeof callback != "function") { + throw new TypeError(); + } + for(var i=0; i 1) { + var newArgs = [C, P].concat( + Array.prototype.slice.call(arguments).slice(1, len-1), F); + OpenLayers.inherit.apply(null, newArgs); + } else { + C.prototype = F; + } + return C; +}; + +/** + * Function: OpenLayers.inherit + * + * Parameters: + * C - {Object} the class that inherits + * P - {Object} the superclass to inherit from + * + * In addition to the mandatory C and P parameters, an arbitrary number of + * objects can be passed, which will extend C. + */ +OpenLayers.inherit = function(C, P) { + var F = function() {}; + F.prototype = P.prototype; + C.prototype = new F; + var i, l, o; + for(i=2, l=arguments.length; i} A cached center location. This should not be + * accessed directly. Use instead. + */ + centerLonLat: null, + + /** + * Constructor: OpenLayers.Bounds + * Construct a new bounds object. Coordinates can either be passed as four + * arguments, or as a single argument. + * + * Parameters (four arguments): + * left - {Number} The left bounds of the box. Note that for width + * calculations, this is assumed to be less than the right value. + * bottom - {Number} The bottom bounds of the box. Note that for height + * calculations, this is assumed to be less than the top value. + * right - {Number} The right bounds. + * top - {Number} The top bounds. + * + * Parameters (single argument): + * bounds - {Array(Number)} [left, bottom, right, top] + */ + initialize: function(left, bottom, right, top) { + if (OpenLayers.Util.isArray(left)) { + top = left[3]; + right = left[2]; + bottom = left[1]; + left = left[0]; + } + if (left != null) { + this.left = OpenLayers.Util.toFloat(left); + } + if (bottom != null) { + this.bottom = OpenLayers.Util.toFloat(bottom); + } + if (right != null) { + this.right = OpenLayers.Util.toFloat(right); + } + if (top != null) { + this.top = OpenLayers.Util.toFloat(top); + } + }, + + /** + * Method: clone + * Create a cloned instance of this bounds. + * + * Returns: + * {} A fresh copy of the bounds + */ + clone:function() { + return new OpenLayers.Bounds(this.left, this.bottom, + this.right, this.top); + }, + + /** + * Method: equals + * Test a two bounds for equivalence. + * + * Parameters: + * bounds - {} + * + * Returns: + * {Boolean} The passed-in bounds object has the same left, + * right, top, bottom components as this. Note that if bounds + * passed in is null, returns false. + */ + equals:function(bounds) { + var equals = false; + if (bounds != null) { + equals = ((this.left == bounds.left) && + (this.right == bounds.right) && + (this.top == bounds.top) && + (this.bottom == bounds.bottom)); + } + return equals; + }, + + /** + * APIMethod: toString + * Returns a string representation of the bounds object. + * + * Returns: + * {String} String representation of bounds object. + */ + toString:function() { + return [this.left, this.bottom, this.right, this.top].join(","); + }, + + /** + * APIMethod: toArray + * Returns an array representation of the bounds object. + * + * Returns an array of left, bottom, right, top properties, or -- when the + * optional parameter is true -- an array of the bottom, left, top, + * right properties. + * + * Parameters: + * reverseAxisOrder - {Boolean} Should we reverse the axis order? + * + * Returns: + * {Array} array of left, bottom, right, top + */ + toArray: function(reverseAxisOrder) { + if (reverseAxisOrder === true) { + return [this.bottom, this.left, this.top, this.right]; + } else { + return [this.left, this.bottom, this.right, this.top]; + } + }, + + /** + * APIMethod: toBBOX + * Returns a boundingbox-string representation of the bounds object. + * + * Parameters: + * decimal - {Integer} How many significant digits in the bbox coords? + * Default is 6 + * reverseAxisOrder - {Boolean} Should we reverse the axis order? + * + * Returns: + * {String} Simple String representation of bounds object. + * (e.g. "5,42,10,45") + */ + toBBOX:function(decimal, reverseAxisOrder) { + if (decimal== null) { + decimal = 6; + } + var mult = Math.pow(10, decimal); + var xmin = Math.round(this.left * mult) / mult; + var ymin = Math.round(this.bottom * mult) / mult; + var xmax = Math.round(this.right * mult) / mult; + var ymax = Math.round(this.top * mult) / mult; + if (reverseAxisOrder === true) { + return ymin + "," + xmin + "," + ymax + "," + xmax; + } else { + return xmin + "," + ymin + "," + xmax + "," + ymax; + } + }, + + /** + * APIMethod: toGeometry + * Create a new polygon geometry based on this bounds. + * + * Returns: + * {} A new polygon with the coordinates + * of this bounds. + */ + toGeometry: function() { + return new OpenLayers.Geometry.Polygon([ + new OpenLayers.Geometry.LinearRing([ + new OpenLayers.Geometry.Point(this.left, this.bottom), + new OpenLayers.Geometry.Point(this.right, this.bottom), + new OpenLayers.Geometry.Point(this.right, this.top), + new OpenLayers.Geometry.Point(this.left, this.top) + ]) + ]); + }, + + /** + * APIMethod: getWidth + * Returns the width of the bounds. + * + * Returns: + * {Float} The width of the bounds (right minus left). + */ + getWidth:function() { + return (this.right - this.left); + }, + + /** + * APIMethod: getHeight + * Returns the height of the bounds. + * + * Returns: + * {Float} The height of the bounds (top minus bottom). + */ + getHeight:function() { + return (this.top - this.bottom); + }, + + /** + * APIMethod: getSize + * Returns an object of the bounds. + * + * Returns: + * {} The size of the bounds. + */ + getSize:function() { + return new OpenLayers.Size(this.getWidth(), this.getHeight()); + }, + + /** + * APIMethod: getCenterPixel + * Returns the object which represents the center of the + * bounds. + * + * Returns: + * {} The center of the bounds in pixel space. + */ + getCenterPixel:function() { + return new OpenLayers.Pixel( (this.left + this.right) / 2, + (this.bottom + this.top) / 2); + }, + + /** + * APIMethod: getCenterLonLat + * Returns the object which represents the center of the + * bounds. + * + * Returns: + * {} The center of the bounds in map space. + */ + getCenterLonLat:function() { + if(!this.centerLonLat) { + this.centerLonLat = new OpenLayers.LonLat( + (this.left + this.right) / 2, (this.bottom + this.top) / 2 + ); + } + return this.centerLonLat; + }, + + /** + * APIMethod: scale + * Scales the bounds around a pixel or lonlat. Note that the new + * bounds may return non-integer properties, even if a pixel + * is passed. + * + * Parameters: + * ratio - {Float} + * origin - { or } + * Default is center. + * + * Returns: + * {} A new bounds that is scaled by ratio + * from origin. + */ + scale: function(ratio, origin){ + if(origin == null){ + origin = this.getCenterLonLat(); + } + + var origx,origy; + + // get origin coordinates + if(origin.CLASS_NAME == "OpenLayers.LonLat"){ + origx = origin.lon; + origy = origin.lat; + } else { + origx = origin.x; + origy = origin.y; + } + + var left = (this.left - origx) * ratio + origx; + var bottom = (this.bottom - origy) * ratio + origy; + var right = (this.right - origx) * ratio + origx; + var top = (this.top - origy) * ratio + origy; + + return new OpenLayers.Bounds(left, bottom, right, top); + }, + + /** + * APIMethod: add + * Shifts the coordinates of the bound by the given horizontal and vertical + * deltas. + * + * (start code) + * var bounds = new OpenLayers.Bounds(0, 0, 10, 10); + * bounds.toString(); + * // => "0,0,10,10" + * + * bounds.add(-1.5, 4).toString(); + * // => "-1.5,4,8.5,14" + * (end) + * + * This method will throw a TypeError if it is passed null as an argument. + * + * Parameters: + * x - {Float} horizontal delta + * y - {Float} vertical delta + * + * Returns: + * {} A new bounds whose coordinates are the same as + * this, but shifted by the passed-in x and y values. + */ + add:function(x, y) { + if ( (x == null) || (y == null) ) { + throw new TypeError('Bounds.add cannot receive null values'); + } + return new OpenLayers.Bounds(this.left + x, this.bottom + y, + this.right + x, this.top + y); + }, + + /** + * APIMethod: extend + * Extend the bounds to include the , + * or specified. + * + * Please note that this function assumes that left < right and + * bottom < top. + * + * Parameters: + * object - {, or + * } The object to be included in the new bounds + * object. + */ + extend:function(object) { + if (object) { + switch(object.CLASS_NAME) { + case "OpenLayers.LonLat": + this.extendXY(object.lon, object.lat); + break; + case "OpenLayers.Geometry.Point": + this.extendXY(object.x, object.y); + break; + + case "OpenLayers.Bounds": + // clear cached center location + this.centerLonLat = null; + + if ( (this.left == null) || (object.left < this.left)) { + this.left = object.left; + } + if ( (this.bottom == null) || (object.bottom < this.bottom) ) { + this.bottom = object.bottom; + } + if ( (this.right == null) || (object.right > this.right) ) { + this.right = object.right; + } + if ( (this.top == null) || (object.top > this.top) ) { + this.top = object.top; + } + break; + } + } + }, + + /** + * APIMethod: extendXY + * Extend the bounds to include the XY coordinate specified. + * + * Parameters: + * x - {number} The X part of the the coordinate. + * y - {number} The Y part of the the coordinate. + */ + extendXY:function(x, y) { + // clear cached center location + this.centerLonLat = null; + + if ((this.left == null) || (x < this.left)) { + this.left = x; + } + if ((this.bottom == null) || (y < this.bottom)) { + this.bottom = y; + } + if ((this.right == null) || (x > this.right)) { + this.right = x; + } + if ((this.top == null) || (y > this.top)) { + this.top = y; + } + }, + + /** + * APIMethod: containsLonLat + * Returns whether the bounds object contains the given . + * + * Parameters: + * ll - {|Object} OpenLayers.LonLat or an + * object with a 'lon' and 'lat' properties. + * options - {Object} Optional parameters + * + * Acceptable options: + * inclusive - {Boolean} Whether or not to include the border. + * Default is true. + * worldBounds - {} If a worldBounds is provided, the + * ll will be considered as contained if it exceeds the world bounds, + * but can be wrapped around the dateline so it is contained by this + * bounds. + * + * Returns: + * {Boolean} The passed-in lonlat is within this bounds. + */ + containsLonLat: function(ll, options) { + if (typeof options === "boolean") { + options = {inclusive: options}; + } + options = options || {}; + var contains = this.contains(ll.lon, ll.lat, options.inclusive), + worldBounds = options.worldBounds; + if (worldBounds && !contains) { + var worldWidth = worldBounds.getWidth(); + var worldCenterX = (worldBounds.left + worldBounds.right) / 2; + var worldsAway = Math.round((ll.lon - worldCenterX) / worldWidth); + contains = this.containsLonLat({ + lon: ll.lon - worldsAway * worldWidth, + lat: ll.lat + }, {inclusive: options.inclusive}); + } + return contains; + }, + + /** + * APIMethod: containsPixel + * Returns whether the bounds object contains the given . + * + * Parameters: + * px - {} + * inclusive - {Boolean} Whether or not to include the border. Default is + * true. + * + * Returns: + * {Boolean} The passed-in pixel is within this bounds. + */ + containsPixel:function(px, inclusive) { + return this.contains(px.x, px.y, inclusive); + }, + + /** + * APIMethod: contains + * Returns whether the bounds object contains the given x and y. + * + * Parameters: + * x - {Float} + * y - {Float} + * inclusive - {Boolean} Whether or not to include the border. Default is + * true. + * + * Returns: + * {Boolean} Whether or not the passed-in coordinates are within this + * bounds. + */ + contains:function(x, y, inclusive) { + //set default + if (inclusive == null) { + inclusive = true; + } + + if (x == null || y == null) { + return false; + } + + x = OpenLayers.Util.toFloat(x); + y = OpenLayers.Util.toFloat(y); + + var contains = false; + if (inclusive) { + contains = ((x >= this.left) && (x <= this.right) && + (y >= this.bottom) && (y <= this.top)); + } else { + contains = ((x > this.left) && (x < this.right) && + (y > this.bottom) && (y < this.top)); + } + return contains; + }, + + /** + * APIMethod: intersectsBounds + * Determine whether the target bounds intersects this bounds. Bounds are + * considered intersecting if any of their edges intersect or if one + * bounds contains the other. + * + * Parameters: + * bounds - {} The target bounds. + * options - {Object} Optional parameters. + * + * Acceptable options: + * inclusive - {Boolean} Treat coincident borders as intersecting. Default + * is true. If false, bounds that do not overlap but only touch at the + * border will not be considered as intersecting. + * worldBounds - {} If a worldBounds is provided, two + * bounds will be considered as intersecting if they intersect when + * shifted to within the world bounds. This applies only to bounds that + * cross or are completely outside the world bounds. + * + * Returns: + * {Boolean} The passed-in bounds object intersects this bounds. + */ + intersectsBounds:function(bounds, options) { + if (typeof options === "boolean") { + options = {inclusive: options}; + } + options = options || {}; + if (options.worldBounds) { + var self = this.wrapDateLine(options.worldBounds); + bounds = bounds.wrapDateLine(options.worldBounds); + } else { + self = this; + } + if (options.inclusive == null) { + options.inclusive = true; + } + var intersects = false; + var mightTouch = ( + self.left == bounds.right || + self.right == bounds.left || + self.top == bounds.bottom || + self.bottom == bounds.top + ); + + // if the two bounds only touch at an edge, and inclusive is false, + // then the bounds don't *really* intersect. + if (options.inclusive || !mightTouch) { + // otherwise, if one of the boundaries even partially contains another, + // inclusive of the edges, then they do intersect. + var inBottom = ( + ((bounds.bottom >= self.bottom) && (bounds.bottom <= self.top)) || + ((self.bottom >= bounds.bottom) && (self.bottom <= bounds.top)) + ); + var inTop = ( + ((bounds.top >= self.bottom) && (bounds.top <= self.top)) || + ((self.top > bounds.bottom) && (self.top < bounds.top)) + ); + var inLeft = ( + ((bounds.left >= self.left) && (bounds.left <= self.right)) || + ((self.left >= bounds.left) && (self.left <= bounds.right)) + ); + var inRight = ( + ((bounds.right >= self.left) && (bounds.right <= self.right)) || + ((self.right >= bounds.left) && (self.right <= bounds.right)) + ); + intersects = ((inBottom || inTop) && (inLeft || inRight)); + } + // document me + if (options.worldBounds && !intersects) { + var world = options.worldBounds; + var width = world.getWidth(); + var selfCrosses = !world.containsBounds(self); + var boundsCrosses = !world.containsBounds(bounds); + if (selfCrosses && !boundsCrosses) { + bounds = bounds.add(-width, 0); + intersects = self.intersectsBounds(bounds, {inclusive: options.inclusive}); + } else if (boundsCrosses && !selfCrosses) { + self = self.add(-width, 0); + intersects = bounds.intersectsBounds(self, {inclusive: options.inclusive}); + } + } + return intersects; + }, + + /** + * APIMethod: containsBounds + * Returns whether the bounds object contains the given . + * + * bounds - {} The target bounds. + * partial - {Boolean} If any of the target corners is within this bounds + * consider the bounds contained. Default is false. If false, the + * entire target bounds must be contained within this bounds. + * inclusive - {Boolean} Treat shared edges as contained. Default is + * true. + * + * Returns: + * {Boolean} The passed-in bounds object is contained within this bounds. + */ + containsBounds:function(bounds, partial, inclusive) { + if (partial == null) { + partial = false; + } + if (inclusive == null) { + inclusive = true; + } + var bottomLeft = this.contains(bounds.left, bounds.bottom, inclusive); + var bottomRight = this.contains(bounds.right, bounds.bottom, inclusive); + var topLeft = this.contains(bounds.left, bounds.top, inclusive); + var topRight = this.contains(bounds.right, bounds.top, inclusive); + + return (partial) ? (bottomLeft || bottomRight || topLeft || topRight) + : (bottomLeft && bottomRight && topLeft && topRight); + }, + + /** + * APIMethod: determineQuadrant + * Returns the the quadrant ("br", "tr", "tl", "bl") in which the given + * lies. + * + * Parameters: + * lonlat - {} + * + * Returns: + * {String} The quadrant ("br" "tr" "tl" "bl") of the bounds in which the + * coordinate lies. + */ + determineQuadrant: function(lonlat) { + + var quadrant = ""; + var center = this.getCenterLonLat(); + + quadrant += (lonlat.lat < center.lat) ? "b" : "t"; + quadrant += (lonlat.lon < center.lon) ? "l" : "r"; + + return quadrant; + }, + + /** + * APIMethod: transform + * Transform the Bounds object from source to dest. + * + * Parameters: + * source - {} Source projection. + * dest - {} Destination projection. + * + * Returns: + * {} Itself, for use in chaining operations. + */ + transform: function(source, dest) { + // clear cached center location + this.centerLonLat = null; + var ll = OpenLayers.Projection.transform( + {'x': this.left, 'y': this.bottom}, source, dest); + var lr = OpenLayers.Projection.transform( + {'x': this.right, 'y': this.bottom}, source, dest); + var ul = OpenLayers.Projection.transform( + {'x': this.left, 'y': this.top}, source, dest); + var ur = OpenLayers.Projection.transform( + {'x': this.right, 'y': this.top}, source, dest); + this.left = Math.min(ll.x, ul.x); + this.bottom = Math.min(ll.y, lr.y); + this.right = Math.max(lr.x, ur.x); + this.top = Math.max(ul.y, ur.y); + return this; + }, + + /** + * APIMethod: wrapDateLine + * Wraps the bounds object around the dateline. + * + * Parameters: + * maxExtent - {} + * options - {Object} Some possible options are: + * + * Allowed Options: + * leftTolerance - {float} Allow for a margin of error + * with the 'left' value of this + * bound. + * Default is 0. + * rightTolerance - {float} Allow for a margin of error + * with the 'right' value of + * this bound. + * Default is 0. + * + * Returns: + * {} A copy of this bounds, but wrapped around the + * "dateline" (as specified by the borders of + * maxExtent). Note that this function only returns + * a different bounds value if this bounds is + * *entirely* outside of the maxExtent. If this + * bounds straddles the dateline (is part in/part + * out of maxExtent), the returned bounds will always + * cross the left edge of the given maxExtent. + *. + */ + wrapDateLine: function(maxExtent, options) { + options = options || {}; + + var leftTolerance = options.leftTolerance || 0; + var rightTolerance = options.rightTolerance || 0; + + var newBounds = this.clone(); + + if (maxExtent) { + var width = maxExtent.getWidth(); + + //shift right? + while (newBounds.left < maxExtent.left && + newBounds.right - rightTolerance <= maxExtent.left ) { + newBounds = newBounds.add(width, 0); + } + + //shift left? + while (newBounds.left + leftTolerance >= maxExtent.right && + newBounds.right > maxExtent.right ) { + newBounds = newBounds.add(-width, 0); + } + + // crosses right only? force left + var newLeft = newBounds.left + leftTolerance; + if (newLeft < maxExtent.right && newLeft > maxExtent.left && + newBounds.right - rightTolerance > maxExtent.right) { + newBounds = newBounds.add(-width, 0); + } + } + + return newBounds; + }, + + CLASS_NAME: "OpenLayers.Bounds" +}); + +/** + * APIFunction: fromString + * Alternative constructor that builds a new OpenLayers.Bounds from a + * parameter string. + * + * (begin code) + * OpenLayers.Bounds.fromString("5,42,10,45"); + * // => equivalent to ... + * new OpenLayers.Bounds(5, 42, 10, 45); + * (end) + * + * Parameters: + * str - {String} Comma-separated bounds string. (e.g. "5,42,10,45") + * reverseAxisOrder - {Boolean} Does the string use reverse axis order? + * + * Returns: + * {} New bounds object built from the + * passed-in String. + */ +OpenLayers.Bounds.fromString = function(str, reverseAxisOrder) { + var bounds = str.split(","); + return OpenLayers.Bounds.fromArray(bounds, reverseAxisOrder); +}; + +/** + * APIFunction: fromArray + * Alternative constructor that builds a new OpenLayers.Bounds from an array. + * + * (begin code) + * OpenLayers.Bounds.fromArray( [5, 42, 10, 45] ); + * // => equivalent to ... + * new OpenLayers.Bounds(5, 42, 10, 45); + * (end) + * + * Parameters: + * bbox - {Array(Float)} Array of bounds values (e.g. [5,42,10,45]) + * reverseAxisOrder - {Boolean} Does the array use reverse axis order? + * + * Returns: + * {} New bounds object built from the passed-in Array. + */ +OpenLayers.Bounds.fromArray = function(bbox, reverseAxisOrder) { + return reverseAxisOrder === true ? + new OpenLayers.Bounds(bbox[1], bbox[0], bbox[3], bbox[2]) : + new OpenLayers.Bounds(bbox[0], bbox[1], bbox[2], bbox[3]); +}; + +/** + * APIFunction: fromSize + * Alternative constructor that builds a new OpenLayers.Bounds from a size. + * + * (begin code) + * OpenLayers.Bounds.fromSize( new OpenLayers.Size(10, 20) ); + * // => equivalent to ... + * new OpenLayers.Bounds(0, 20, 10, 0); + * (end) + * + * Parameters: + * size - { or Object} or an object with + * both 'w' and 'h' properties. + * + * Returns: + * {} New bounds object built from the passed-in size. + */ +OpenLayers.Bounds.fromSize = function(size) { + return new OpenLayers.Bounds(0, + size.h, + size.w, + 0); +}; + +/** + * Function: oppositeQuadrant + * Get the opposite quadrant for a given quadrant string. + * + * (begin code) + * OpenLayers.Bounds.oppositeQuadrant( "tl" ); + * // => "br" + * + * OpenLayers.Bounds.oppositeQuadrant( "tr" ); + * // => "bl" + * (end) + * + * Parameters: + * quadrant - {String} two character quadrant shortstring + * + * Returns: + * {String} The opposing quadrant ("br" "tr" "tl" "bl"). For Example, if + * you pass in "bl" it returns "tr", if you pass in "br" it + * returns "tl", etc. + */ +OpenLayers.Bounds.oppositeQuadrant = function(quadrant) { + var opp = ""; + + opp += (quadrant.charAt(0) == 't') ? 'b' : 't'; + opp += (quadrant.charAt(1) == 'l') ? 'r' : 'l'; + + return opp; +}; +/* ====================================================================== + OpenLayers/BaseTypes/Element.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Util.js + * @requires OpenLayers/BaseTypes.js + */ + +/** + * Namespace: OpenLayers.Element + */ +OpenLayers.Element = { + + /** + * APIFunction: visible + * + * Parameters: + * element - {DOMElement} + * + * Returns: + * {Boolean} Is the element visible? + */ + visible: function(element) { + return OpenLayers.Util.getElement(element).style.display != 'none'; + }, + + /** + * APIFunction: toggle + * Toggle the visibility of element(s) passed in + * + * Parameters: + * element - {DOMElement} Actually user can pass any number of elements + */ + toggle: function() { + for (var i=0, len=arguments.length; i"lon=5,lat=42") + */ + toString:function() { + return ("lon=" + this.lon + ",lat=" + this.lat); + }, + + /** + * APIMethod: toShortString + * + * Returns: + * {String} Shortened String representation of OpenLayers.LonLat object. + * (e.g. "5, 42") + */ + toShortString:function() { + return (this.lon + ", " + this.lat); + }, + + /** + * APIMethod: clone + * + * Returns: + * {} New OpenLayers.LonLat object with the same lon + * and lat values + */ + clone:function() { + return new OpenLayers.LonLat(this.lon, this.lat); + }, + + /** + * APIMethod: add + * + * Parameters: + * lon - {Float} + * lat - {Float} + * + * Returns: + * {} A new OpenLayers.LonLat object with the lon and + * lat passed-in added to this's. + */ + add:function(lon, lat) { + if ( (lon == null) || (lat == null) ) { + throw new TypeError('LonLat.add cannot receive null values'); + } + return new OpenLayers.LonLat(this.lon + OpenLayers.Util.toFloat(lon), + this.lat + OpenLayers.Util.toFloat(lat)); + }, + + /** + * APIMethod: equals + * + * Parameters: + * ll - {} + * + * Returns: + * {Boolean} Boolean value indicating whether the passed-in + * object has the same lon and lat + * components as this. + * Note: if ll passed in is null, returns false + */ + equals:function(ll) { + var equals = false; + if (ll != null) { + equals = ((this.lon == ll.lon && this.lat == ll.lat) || + (isNaN(this.lon) && isNaN(this.lat) && isNaN(ll.lon) && isNaN(ll.lat))); + } + return equals; + }, + + /** + * APIMethod: transform + * Transform the LonLat object from source to dest. This transformation is + * *in place*: if you want a *new* lonlat, use .clone() first. + * + * Parameters: + * source - {} Source projection. + * dest - {} Destination projection. + * + * Returns: + * {} Itself, for use in chaining operations. + */ + transform: function(source, dest) { + var point = OpenLayers.Projection.transform( + {'x': this.lon, 'y': this.lat}, source, dest); + this.lon = point.x; + this.lat = point.y; + return this; + }, + + /** + * APIMethod: wrapDateLine + * + * Parameters: + * maxExtent - {} + * + * Returns: + * {} A copy of this lonlat, but wrapped around the + * "dateline" (as specified by the borders of + * maxExtent) + */ + wrapDateLine: function(maxExtent) { + + var newLonLat = this.clone(); + + if (maxExtent) { + //shift right? + while (newLonLat.lon < maxExtent.left) { + newLonLat.lon += maxExtent.getWidth(); + } + + //shift left? + while (newLonLat.lon > maxExtent.right) { + newLonLat.lon -= maxExtent.getWidth(); + } + } + + return newLonLat; + }, + + CLASS_NAME: "OpenLayers.LonLat" +}); + +/** + * Function: fromString + * Alternative constructor that builds a new from a + * parameter string + * + * Parameters: + * str - {String} Comma-separated Lon,Lat coordinate string. + * (e.g. "5,40") + * + * Returns: + * {} New object built from the + * passed-in String. + */ +OpenLayers.LonLat.fromString = function(str) { + var pair = str.split(","); + return new OpenLayers.LonLat(pair[0], pair[1]); +}; + +/** + * Function: fromArray + * Alternative constructor that builds a new from an + * array of two numbers that represent lon- and lat-values. + * + * Parameters: + * arr - {Array(Float)} Array of lon/lat values (e.g. [5,-42]) + * + * Returns: + * {} New object built from the + * passed-in array. + */ +OpenLayers.LonLat.fromArray = function(arr) { + var gotArr = OpenLayers.Util.isArray(arr), + lon = gotArr && arr[0], + lat = gotArr && arr[1]; + return new OpenLayers.LonLat(lon, lat); +}; +/* ====================================================================== + OpenLayers/BaseTypes/Pixel.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/BaseTypes/Class.js + */ + +/** + * Class: OpenLayers.Pixel + * This class represents a screen coordinate, in x and y coordinates + */ +OpenLayers.Pixel = OpenLayers.Class({ + + /** + * APIProperty: x + * {Number} The x coordinate + */ + x: 0.0, + + /** + * APIProperty: y + * {Number} The y coordinate + */ + y: 0.0, + + /** + * Constructor: OpenLayers.Pixel + * Create a new OpenLayers.Pixel instance + * + * Parameters: + * x - {Number} The x coordinate + * y - {Number} The y coordinate + * + * Returns: + * An instance of OpenLayers.Pixel + */ + initialize: function(x, y) { + this.x = parseFloat(x); + this.y = parseFloat(y); + }, + + /** + * Method: toString + * Cast this object into a string + * + * Returns: + * {String} The string representation of Pixel. ex: "x=200.4,y=242.2" + */ + toString:function() { + return ("x=" + this.x + ",y=" + this.y); + }, + + /** + * APIMethod: clone + * Return a clone of this pixel object + * + * Returns: + * {} A clone pixel + */ + clone:function() { + return new OpenLayers.Pixel(this.x, this.y); + }, + + /** + * APIMethod: equals + * Determine whether one pixel is equivalent to another + * + * Parameters: + * px - {|Object} An OpenLayers.Pixel or an object with + * a 'x' and 'y' properties. + * + * Returns: + * {Boolean} The point passed in as parameter is equal to this. Note that + * if px passed in is null, returns false. + */ + equals:function(px) { + var equals = false; + if (px != null) { + equals = ((this.x == px.x && this.y == px.y) || + (isNaN(this.x) && isNaN(this.y) && isNaN(px.x) && isNaN(px.y))); + } + return equals; + }, + + /** + * APIMethod: distanceTo + * Returns the distance to the pixel point passed in as a parameter. + * + * Parameters: + * px - {} + * + * Returns: + * {Float} The pixel point passed in as parameter to calculate the + * distance to. + */ + distanceTo:function(px) { + return Math.sqrt( + Math.pow(this.x - px.x, 2) + + Math.pow(this.y - px.y, 2) + ); + }, + + /** + * APIMethod: add + * + * Parameters: + * x - {Integer} + * y - {Integer} + * + * Returns: + * {} A new Pixel with this pixel's x&y augmented by the + * values passed in. + */ + add:function(x, y) { + if ( (x == null) || (y == null) ) { + throw new TypeError('Pixel.add cannot receive null values'); + } + return new OpenLayers.Pixel(this.x + x, this.y + y); + }, + + /** + * APIMethod: offset + * + * Parameters + * px - {|Object} An OpenLayers.Pixel or an object with + * a 'x' and 'y' properties. + * + * Returns: + * {} A new Pixel with this pixel's x&y augmented by the + * x&y values of the pixel passed in. + */ + offset:function(px) { + var newPx = this.clone(); + if (px) { + newPx = this.add(px.x, px.y); + } + return newPx; + }, + + CLASS_NAME: "OpenLayers.Pixel" +}); +/* ====================================================================== + OpenLayers/BaseTypes/Size.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/BaseTypes/Class.js + */ + +/** + * Class: OpenLayers.Size + * Instances of this class represent a width/height pair + */ +OpenLayers.Size = OpenLayers.Class({ + + /** + * APIProperty: w + * {Number} width + */ + w: 0.0, + + /** + * APIProperty: h + * {Number} height + */ + h: 0.0, + + + /** + * Constructor: OpenLayers.Size + * Create an instance of OpenLayers.Size + * + * Parameters: + * w - {Number} width + * h - {Number} height + */ + initialize: function(w, h) { + this.w = parseFloat(w); + this.h = parseFloat(h); + }, + + /** + * Method: toString + * Return the string representation of a size object + * + * Returns: + * {String} The string representation of OpenLayers.Size object. + * (e.g. "w=55,h=66") + */ + toString:function() { + return ("w=" + this.w + ",h=" + this.h); + }, + + /** + * APIMethod: clone + * Create a clone of this size object + * + * Returns: + * {} A new OpenLayers.Size object with the same w and h + * values + */ + clone:function() { + return new OpenLayers.Size(this.w, this.h); + }, + + /** + * + * APIMethod: equals + * Determine where this size is equal to another + * + * Parameters: + * sz - {|Object} An OpenLayers.Size or an object with + * a 'w' and 'h' properties. + * + * Returns: + * {Boolean} The passed in size has the same h and w properties as this one. + * Note that if sz passed in is null, returns false. + */ + equals:function(sz) { + var equals = false; + if (sz != null) { + equals = ((this.w == sz.w && this.h == sz.h) || + (isNaN(this.w) && isNaN(this.h) && isNaN(sz.w) && isNaN(sz.h))); + } + return equals; + }, + + CLASS_NAME: "OpenLayers.Size" +}); +/* ====================================================================== + OpenLayers/Console.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/BaseTypes/Class.js + */ + +/** + * Namespace: OpenLayers.Console + * The OpenLayers.Console namespace is used for debugging and error logging. + * If the Firebug Lite (../Firebug/firebug.js) is included before this script, + * calls to OpenLayers.Console methods will get redirected to window.console. + * This makes use of the Firebug extension where available and allows for + * cross-browser debugging Firebug style. + * + * Note: + * Note that behavior will differ with the Firebug extention and Firebug Lite. + * Most notably, the Firebug Lite console does not currently allow for + * hyperlinks to code or for clicking on object to explore their properties. + * + */ +OpenLayers.Console = { + /** + * Create empty functions for all console methods. The real value of these + * properties will be set if Firebug Lite (../Firebug/firebug.js script) is + * included. We explicitly require the Firebug Lite script to trigger + * functionality of the OpenLayers.Console methods. + */ + + /** + * APIFunction: log + * Log an object in the console. The Firebug Lite console logs string + * representation of objects. Given multiple arguments, they will + * be cast to strings and logged with a space delimiter. If the first + * argument is a string with printf-like formatting, subsequent arguments + * will be used in string substitution. Any additional arguments (beyond + * the number substituted in a format string) will be appended in a space- + * delimited line. + * + * Parameters: + * object - {Object} + */ + log: function() {}, + + /** + * APIFunction: debug + * Writes a message to the console, including a hyperlink to the line + * where it was called. + * + * May be called with multiple arguments as with OpenLayers.Console.log(). + * + * Parameters: + * object - {Object} + */ + debug: function() {}, + + /** + * APIFunction: info + * Writes a message to the console with the visual "info" icon and color + * coding and a hyperlink to the line where it was called. + * + * May be called with multiple arguments as with OpenLayers.Console.log(). + * + * Parameters: + * object - {Object} + */ + info: function() {}, + + /** + * APIFunction: warn + * Writes a message to the console with the visual "warning" icon and + * color coding and a hyperlink to the line where it was called. + * + * May be called with multiple arguments as with OpenLayers.Console.log(). + * + * Parameters: + * object - {Object} + */ + warn: function() {}, + + /** + * APIFunction: error + * Writes a message to the console with the visual "error" icon and color + * coding and a hyperlink to the line where it was called. + * + * May be called with multiple arguments as with OpenLayers.Console.log(). + * + * Parameters: + * object - {Object} + */ + error: function() {}, + + /** + * APIFunction: userError + * A single interface for showing error messages to the user. The default + * behavior is a Javascript alert, though this can be overridden by + * reassigning OpenLayers.Console.userError to a different function. + * + * Expects a single error message + * + * Parameters: + * error - {Object} + */ + userError: function(error) { + alert(error); + }, + + /** + * APIFunction: assert + * Tests that an expression is true. If not, it will write a message to + * the console and throw an exception. + * + * May be called with multiple arguments as with OpenLayers.Console.log(). + * + * Parameters: + * object - {Object} + */ + assert: function() {}, + + /** + * APIFunction: dir + * Prints an interactive listing of all properties of the object. This + * looks identical to the view that you would see in the DOM tab. + * + * Parameters: + * object - {Object} + */ + dir: function() {}, + + /** + * APIFunction: dirxml + * Prints the XML source tree of an HTML or XML element. This looks + * identical to the view that you would see in the HTML tab. You can click + * on any node to inspect it in the HTML tab. + * + * Parameters: + * object - {Object} + */ + dirxml: function() {}, + + /** + * APIFunction: trace + * Prints an interactive stack trace of JavaScript execution at the point + * where it is called. The stack trace details the functions on the stack, + * as well as the values that were passed as arguments to each function. + * You can click each function to take you to its source in the Script tab, + * and click each argument value to inspect it in the DOM or HTML tabs. + * + */ + trace: function() {}, + + /** + * APIFunction: group + * Writes a message to the console and opens a nested block to indent all + * future messages sent to the console. Call OpenLayers.Console.groupEnd() + * to close the block. + * + * May be called with multiple arguments as with OpenLayers.Console.log(). + * + * Parameters: + * object - {Object} + */ + group: function() {}, + + /** + * APIFunction: groupEnd + * Closes the most recently opened block created by a call to + * OpenLayers.Console.group + */ + groupEnd: function() {}, + + /** + * APIFunction: time + * Creates a new timer under the given name. Call + * OpenLayers.Console.timeEnd(name) + * with the same name to stop the timer and print the time elapsed. + * + * Parameters: + * name - {String} + */ + time: function() {}, + + /** + * APIFunction: timeEnd + * Stops a timer created by a call to OpenLayers.Console.time(name) and + * writes the time elapsed. + * + * Parameters: + * name - {String} + */ + timeEnd: function() {}, + + /** + * APIFunction: profile + * Turns on the JavaScript profiler. The optional argument title would + * contain the text to be printed in the header of the profile report. + * + * This function is not currently implemented in Firebug Lite. + * + * Parameters: + * title - {String} Optional title for the profiler + */ + profile: function() {}, + + /** + * APIFunction: profileEnd + * Turns off the JavaScript profiler and prints its report. + * + * This function is not currently implemented in Firebug Lite. + */ + profileEnd: function() {}, + + /** + * APIFunction: count + * Writes the number of times that the line of code where count was called + * was executed. The optional argument title will print a message in + * addition to the number of the count. + * + * This function is not currently implemented in Firebug Lite. + * + * Parameters: + * title - {String} Optional title to be printed with count + */ + count: function() {}, + + CLASS_NAME: "OpenLayers.Console" +}; + +/** + * Execute an anonymous function to extend the OpenLayers.Console namespace + * if the firebug.js script is included. This closure is used so that the + * "scripts" and "i" variables don't pollute the global namespace. + */ +(function() { + /** + * If Firebug Lite is included (before this script), re-route all + * OpenLayers.Console calls to the console object. + */ + var scripts = document.getElementsByTagName("script"); + for(var i=0, len=scripts.length; i method to set this value and the method to + * retrieve it. + */ + code: null, + + /** + * APIProperty: defaultCode + * {String} Default language to use when a specific language can't be + * found. Default is "en". + */ + defaultCode: "en", + + /** + * APIFunction: getCode + * Get the current language code. + * + * Returns: + * {String} The current language code. + */ + getCode: function() { + if(!OpenLayers.Lang.code) { + OpenLayers.Lang.setCode(); + } + return OpenLayers.Lang.code; + }, + + /** + * APIFunction: setCode + * Set the language code for string translation. This code is used by + * the method. + * + * Parameters: + * code - {String} These codes follow the IETF recommendations at + * http://www.ietf.org/rfc/rfc3066.txt. If no value is set, the + * browser's language setting will be tested. If no + * dictionary exists for the code, the + * will be used. + */ + setCode: function(code) { + var lang; + if(!code) { + code = (OpenLayers.BROWSER_NAME == "msie") ? + navigator.userLanguage : navigator.language; + } + var parts = code.split('-'); + parts[0] = parts[0].toLowerCase(); + if(typeof OpenLayers.Lang[parts[0]] == "object") { + lang = parts[0]; + } + + // check for regional extensions + if(parts[1]) { + var testLang = parts[0] + '-' + parts[1].toUpperCase(); + if(typeof OpenLayers.Lang[testLang] == "object") { + lang = testLang; + } + } + if(!lang) { + OpenLayers.Console.warn( + 'Failed to find OpenLayers.Lang.' + parts.join("-") + + ' dictionary, falling back to default language' + ); + lang = OpenLayers.Lang.defaultCode; + } + + OpenLayers.Lang.code = lang; + }, + + /** + * APIMethod: translate + * Looks up a key from a dictionary based on the current language string. + * The value of will be used to determine the appropriate + * dictionary. Dictionaries are stored in . + * + * Parameters: + * key - {String} The key for an i18n string value in the dictionary. + * context - {Object} Optional context to be used with + * . + * + * Returns: + * {String} A internationalized string. + */ + translate: function(key, context) { + var dictionary = OpenLayers.Lang[OpenLayers.Lang.getCode()]; + var message = dictionary && dictionary[key]; + if(!message) { + // Message not found, fall back to message key + message = key; + } + if(context) { + message = OpenLayers.String.format(message, context); + } + return message; + } + +}; + + +/** + * APIMethod: OpenLayers.i18n + * Alias for . Looks up a key from a dictionary + * based on the current language string. The value of + * will be used to determine the appropriate + * dictionary. Dictionaries are stored in . + * + * Parameters: + * key - {String} The key for an i18n string value in the dictionary. + * context - {Object} Optional context to be used with + * . + * + * Returns: + * {String} A internationalized string. + */ +OpenLayers.i18n = OpenLayers.Lang.translate; +/* ====================================================================== + OpenLayers/Util.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/BaseTypes.js + * @requires OpenLayers/BaseTypes/Bounds.js + * @requires OpenLayers/BaseTypes/Element.js + * @requires OpenLayers/BaseTypes/LonLat.js + * @requires OpenLayers/BaseTypes/Pixel.js + * @requires OpenLayers/BaseTypes/Size.js + * @requires OpenLayers/Lang.js + */ + +/** + * Namespace: Util + */ +OpenLayers.Util = OpenLayers.Util || {}; + +/** + * Function: getElement + * This is the old $() from prototype + * + * Parameters: + * e - {String or DOMElement or Window} + * + * Returns: + * {Array(DOMElement) or DOMElement} + */ +OpenLayers.Util.getElement = function() { + var elements = []; + + for (var i=0, len=arguments.length; i= 0; i--) { + if(array[i] == item) { + array.splice(i,1); + //break;more than once?? + } + } + return array; +}; + +/** + * Function: indexOf + * Seems to exist already in FF, but not in MOZ. + * + * Parameters: + * array - {Array} + * obj - {*} + * + * Returns: + * {Integer} The index at which the first object was found in the array. + * If not found, returns -1. + */ +OpenLayers.Util.indexOf = function(array, obj) { + // use the build-in function if available. + if (typeof array.indexOf == "function") { + return array.indexOf(obj); + } else { + for (var i = 0, len = array.length; i < len; i++) { + if (array[i] == obj) { + return i; + } + } + return -1; + } +}; + + +/** + * Property: dotless + * {RegExp} + * Compiled regular expression to match dots ("."). This is used for replacing + * dots in identifiers. Because object identifiers are frequently used for + * DOM element identifiers by the library, we avoid using dots to make for + * more sensible CSS selectors. + * + * TODO: Use a module pattern to avoid bloating the API with stuff like this. + */ +OpenLayers.Util.dotless = /\./g; + +/** + * Function: modifyDOMElement + * + * Modifies many properties of a DOM element all at once. Passing in + * null to an individual parameter will avoid setting the attribute. + * + * Parameters: + * element - {DOMElement} DOM element to modify. + * id - {String} The element id attribute to set. Note that dots (".") will be + * replaced with underscore ("_") in setting the element id. + * px - {|Object} The element left and top position, + * OpenLayers.Pixel or an object with + * a 'x' and 'y' properties. + * sz - {|Object} The element width and height, + * OpenLayers.Size or an object with a + * 'w' and 'h' properties. + * position - {String} The position attribute. eg: absolute, + * relative, etc. + * border - {String} The style.border attribute. eg: + * solid black 2px + * overflow - {String} The style.overview attribute. + * opacity - {Float} Fractional value (0.0 - 1.0) + */ +OpenLayers.Util.modifyDOMElement = function(element, id, px, sz, position, + border, overflow, opacity) { + + if (id) { + element.id = id.replace(OpenLayers.Util.dotless, "_"); + } + if (px) { + element.style.left = px.x + "px"; + element.style.top = px.y + "px"; + } + if (sz) { + element.style.width = sz.w + "px"; + element.style.height = sz.h + "px"; + } + if (position) { + element.style.position = position; + } + if (border) { + element.style.border = border; + } + if (overflow) { + element.style.overflow = overflow; + } + if (parseFloat(opacity) >= 0.0 && parseFloat(opacity) < 1.0) { + element.style.filter = 'alpha(opacity=' + (opacity * 100) + ')'; + element.style.opacity = opacity; + } else if (parseFloat(opacity) == 1.0) { + element.style.filter = ''; + element.style.opacity = ''; + } +}; + +/** + * Function: createDiv + * Creates a new div and optionally set some standard attributes. + * Null may be passed to each parameter if you do not wish to + * set a particular attribute. + * Note - zIndex is NOT set on the resulting div. + * + * Parameters: + * id - {String} An identifier for this element. If no id is + * passed an identifier will be created + * automatically. Note that dots (".") will be replaced with + * underscore ("_") when generating ids. + * px - {|Object} The element left and top position, + * OpenLayers.Pixel or an object with + * a 'x' and 'y' properties. + * sz - {|Object} The element width and height, + * OpenLayers.Size or an object with a + * 'w' and 'h' properties. + * imgURL - {String} A url pointing to an image to use as a + * background image. + * position - {String} The style.position value. eg: absolute, + * relative etc. + * border - {String} The the style.border value. + * eg: 2px solid black + * overflow - {String} The style.overflow value. Eg. hidden + * opacity - {Float} Fractional value (0.0 - 1.0) + * + * Returns: + * {DOMElement} A DOM Div created with the specified attributes. + */ +OpenLayers.Util.createDiv = function(id, px, sz, imgURL, position, + border, overflow, opacity) { + + var dom = document.createElement('div'); + + if (imgURL) { + dom.style.backgroundImage = 'url(' + imgURL + ')'; + } + + //set generic properties + if (!id) { + id = OpenLayers.Util.createUniqueID("OpenLayersDiv"); + } + if (!position) { + position = "absolute"; + } + OpenLayers.Util.modifyDOMElement(dom, id, px, sz, position, + border, overflow, opacity); + + return dom; +}; + +/** + * Function: createImage + * Creates an img element with specific attribute values. + * + * Parameters: + * id - {String} The id field for the img. If none assigned one will be + * automatically generated. + * px - {|Object} The element left and top position, + * OpenLayers.Pixel or an object with + * a 'x' and 'y' properties. + * sz - {|Object} The element width and height, + * OpenLayers.Size or an object with a + * 'w' and 'h' properties. + * imgURL - {String} The url to use as the image source. + * position - {String} The style.position value. + * border - {String} The border to place around the image. + * opacity - {Float} Fractional value (0.0 - 1.0) + * delayDisplay - {Boolean} If true waits until the image has been + * loaded. + * + * Returns: + * {DOMElement} A DOM Image created with the specified attributes. + */ +OpenLayers.Util.createImage = function(id, px, sz, imgURL, position, border, + opacity, delayDisplay) { + + var image = document.createElement("img"); + + //set generic properties + if (!id) { + id = OpenLayers.Util.createUniqueID("OpenLayersDiv"); + } + if (!position) { + position = "relative"; + } + OpenLayers.Util.modifyDOMElement(image, id, px, sz, position, + border, null, opacity); + + if (delayDisplay) { + image.style.display = "none"; + function display() { + image.style.display = ""; + OpenLayers.Event.stopObservingElement(image); + } + OpenLayers.Event.observe(image, "load", display); + OpenLayers.Event.observe(image, "error", display); + } + + //set special properties + image.style.alt = id; + image.galleryImg = "no"; + if (imgURL) { + image.src = imgURL; + } + + return image; +}; + +/** + * Property: IMAGE_RELOAD_ATTEMPTS + * {Integer} How many times should we try to reload an image before giving up? + * Default is 0 + */ +OpenLayers.IMAGE_RELOAD_ATTEMPTS = 0; + +/** + * Property: alphaHackNeeded + * {Boolean} true if the png alpha hack is necessary and possible, false otherwise. + */ +OpenLayers.Util.alphaHackNeeded = null; + +/** + * Function: alphaHack + * Checks whether it's necessary (and possible) to use the png alpha + * hack which allows alpha transparency for png images under Internet + * Explorer. + * + * Returns: + * {Boolean} true if the png alpha hack is necessary and possible, false otherwise. + */ +OpenLayers.Util.alphaHack = function() { + if (OpenLayers.Util.alphaHackNeeded == null) { + var arVersion = navigator.appVersion.split("MSIE"); + var version = parseFloat(arVersion[1]); + var filter = false; + + // IEs4Lin dies when trying to access document.body.filters, because + // the property is there, but requires a DLL that can't be provided. This + // means that we need to wrap this in a try/catch so that this can + // continue. + + try { + filter = !!(document.body.filters); + } catch (e) {} + + OpenLayers.Util.alphaHackNeeded = (filter && + (version >= 5.5) && (version < 7)); + } + return OpenLayers.Util.alphaHackNeeded; +}; + +/** + * Function: modifyAlphaImageDiv + * + * Parameters: + * div - {DOMElement} Div containing Alpha-adjusted Image + * id - {String} + * px - {|Object} OpenLayers.Pixel or an object with + * a 'x' and 'y' properties. + * sz - {|Object} OpenLayers.Size or an object with + * a 'w' and 'h' properties. + * imgURL - {String} + * position - {String} + * border - {String} + * sizing - {String} 'crop', 'scale', or 'image'. Default is "scale" + * opacity - {Float} Fractional value (0.0 - 1.0) + */ +OpenLayers.Util.modifyAlphaImageDiv = function(div, id, px, sz, imgURL, + position, border, sizing, + opacity) { + + OpenLayers.Util.modifyDOMElement(div, id, px, sz, position, + null, null, opacity); + + var img = div.childNodes[0]; + + if (imgURL) { + img.src = imgURL; + } + OpenLayers.Util.modifyDOMElement(img, div.id + "_innerImage", null, sz, + "relative", border); + + if (OpenLayers.Util.alphaHack()) { + if(div.style.display != "none") { + div.style.display = "inline-block"; + } + if (sizing == null) { + sizing = "scale"; + } + + div.style.filter = "progid:DXImageTransform.Microsoft" + + ".AlphaImageLoader(src='" + img.src + "', " + + "sizingMethod='" + sizing + "')"; + if (parseFloat(div.style.opacity) >= 0.0 && + parseFloat(div.style.opacity) < 1.0) { + div.style.filter += " alpha(opacity=" + div.style.opacity * 100 + ")"; + } + + img.style.filter = "alpha(opacity=0)"; + } +}; + +/** + * Function: createAlphaImageDiv + * + * Parameters: + * id - {String} + * px - {|Object} OpenLayers.Pixel or an object with + * a 'x' and 'y' properties. + * sz - {|Object} OpenLayers.Size or an object with + * a 'w' and 'h' properties. + * imgURL - {String} + * position - {String} + * border - {String} + * sizing - {String} 'crop', 'scale', or 'image'. Default is "scale" + * opacity - {Float} Fractional value (0.0 - 1.0) + * delayDisplay - {Boolean} If true waits until the image has been + * loaded. + * + * Returns: + * {DOMElement} A DOM Div created with a DOM Image inside it. If the hack is + * needed for transparency in IE, it is added. + */ +OpenLayers.Util.createAlphaImageDiv = function(id, px, sz, imgURL, + position, border, sizing, + opacity, delayDisplay) { + + var div = OpenLayers.Util.createDiv(); + var img = OpenLayers.Util.createImage(null, null, null, null, null, null, + null, delayDisplay); + img.className = "olAlphaImg"; + div.appendChild(img); + + OpenLayers.Util.modifyAlphaImageDiv(div, id, px, sz, imgURL, position, + border, sizing, opacity); + + return div; +}; + + +/** + * Function: upperCaseObject + * Creates a new hashtable and copies over all the keys from the + * passed-in object, but storing them under an uppercased + * version of the key at which they were stored. + * + * Parameters: + * object - {Object} + * + * Returns: + * {Object} A new Object with all the same keys but uppercased + */ +OpenLayers.Util.upperCaseObject = function (object) { + var uObject = {}; + for (var key in object) { + uObject[key.toUpperCase()] = object[key]; + } + return uObject; +}; + +/** + * Function: applyDefaults + * Takes an object and copies any properties that don't exist from + * another properties, by analogy with OpenLayers.Util.extend() from + * Prototype.js. + * + * Parameters: + * to - {Object} The destination object. + * from - {Object} The source object. Any properties of this object that + * are undefined in the to object will be set on the to object. + * + * Returns: + * {Object} A reference to the to object. Note that the to argument is modified + * in place and returned by this function. + */ +OpenLayers.Util.applyDefaults = function (to, from) { + to = to || {}; + /* + * FF/Windows < 2.0.0.13 reports "Illegal operation on WrappedNative + * prototype object" when calling hawOwnProperty if the source object is an + * instance of window.Event. + */ + var fromIsEvt = typeof window.Event == "function" + && from instanceof window.Event; + + for (var key in from) { + if (to[key] === undefined || + (!fromIsEvt && from.hasOwnProperty + && from.hasOwnProperty(key) && !to.hasOwnProperty(key))) { + to[key] = from[key]; + } + } + /** + * IE doesn't include the toString property when iterating over an object's + * properties with the for(property in object) syntax. Explicitly check if + * the source has its own toString property. + */ + if(!fromIsEvt && from && from.hasOwnProperty + && from.hasOwnProperty('toString') && !to.hasOwnProperty('toString')) { + to.toString = from.toString; + } + + return to; +}; + +/** + * Function: getParameterString + * + * Parameters: + * params - {Object} + * + * Returns: + * {String} A concatenation of the properties of an object in + * http parameter notation. + * (ex. "key1=value1&key2=value2&key3=value3") + * If a parameter is actually a list, that parameter will then + * be set to a comma-seperated list of values (foo,bar) instead + * of being URL escaped (foo%3Abar). + */ +OpenLayers.Util.getParameterString = function(params) { + var paramsArray = []; + + for (var key in params) { + var value = params[key]; + if ((value != null) && (typeof value != 'function')) { + var encodedValue; + if (typeof value == 'object' && value.constructor == Array) { + /* value is an array; encode items and separate with "," */ + var encodedItemArray = []; + var item; + for (var itemIndex=0, len=value.length; itemIndex} (or any object with both .lat, .lon properties) + * p2 - {} (or any object with both .lat, .lon properties) + * + * Returns: + * {Float} The distance (in km) between the two input points as measured on an + * ellipsoid. Note that the input point objects must be in geographic + * coordinates (decimal degrees) and the return distance is in kilometers. + */ +OpenLayers.Util.distVincenty = function(p1, p2) { + var ct = OpenLayers.Util.VincentyConstants; + var a = ct.a, b = ct.b, f = ct.f; + + var L = OpenLayers.Util.rad(p2.lon - p1.lon); + var U1 = Math.atan((1-f) * Math.tan(OpenLayers.Util.rad(p1.lat))); + var U2 = Math.atan((1-f) * Math.tan(OpenLayers.Util.rad(p2.lat))); + var sinU1 = Math.sin(U1), cosU1 = Math.cos(U1); + var sinU2 = Math.sin(U2), cosU2 = Math.cos(U2); + var lambda = L, lambdaP = 2*Math.PI; + var iterLimit = 20; + while (Math.abs(lambda-lambdaP) > 1e-12 && --iterLimit>0) { + var sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda); + var sinSigma = Math.sqrt((cosU2*sinLambda) * (cosU2*sinLambda) + + (cosU1*sinU2-sinU1*cosU2*cosLambda) * (cosU1*sinU2-sinU1*cosU2*cosLambda)); + if (sinSigma==0) { + return 0; // co-incident points + } + var cosSigma = sinU1*sinU2 + cosU1*cosU2*cosLambda; + var sigma = Math.atan2(sinSigma, cosSigma); + var alpha = Math.asin(cosU1 * cosU2 * sinLambda / sinSigma); + var cosSqAlpha = Math.cos(alpha) * Math.cos(alpha); + var cos2SigmaM = cosSigma - 2*sinU1*sinU2/cosSqAlpha; + var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha)); + lambdaP = lambda; + lambda = L + (1-C) * f * Math.sin(alpha) * + (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM))); + } + if (iterLimit==0) { + return NaN; // formula failed to converge + } + var uSq = cosSqAlpha * (a*a - b*b) / (b*b); + var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq))); + var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq))); + var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)- + B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM))); + var s = b*A*(sigma-deltaSigma); + var d = s.toFixed(3)/1000; // round to 1mm precision + return d; +}; + +/** + * APIFunction: destinationVincenty + * Calculate destination point given start point lat/long (numeric degrees), + * bearing (numeric degrees) & distance (in m). + * Adapted from Chris Veness work, see + * http://www.movable-type.co.uk/scripts/latlong-vincenty-direct.html + * + * Parameters: + * lonlat - {} (or any object with both .lat, .lon + * properties) The start point. + * brng - {Float} The bearing (degrees). + * dist - {Float} The ground distance (meters). + * + * Returns: + * {} The destination point. + */ +OpenLayers.Util.destinationVincenty = function(lonlat, brng, dist) { + var u = OpenLayers.Util; + var ct = u.VincentyConstants; + var a = ct.a, b = ct.b, f = ct.f; + + var lon1 = lonlat.lon; + var lat1 = lonlat.lat; + + var s = dist; + var alpha1 = u.rad(brng); + var sinAlpha1 = Math.sin(alpha1); + var cosAlpha1 = Math.cos(alpha1); + + var tanU1 = (1-f) * Math.tan(u.rad(lat1)); + var cosU1 = 1 / Math.sqrt((1 + tanU1*tanU1)), sinU1 = tanU1*cosU1; + var sigma1 = Math.atan2(tanU1, cosAlpha1); + var sinAlpha = cosU1 * sinAlpha1; + var cosSqAlpha = 1 - sinAlpha*sinAlpha; + var uSq = cosSqAlpha * (a*a - b*b) / (b*b); + var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq))); + var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq))); + + var sigma = s / (b*A), sigmaP = 2*Math.PI; + while (Math.abs(sigma-sigmaP) > 1e-12) { + var cos2SigmaM = Math.cos(2*sigma1 + sigma); + var sinSigma = Math.sin(sigma); + var cosSigma = Math.cos(sigma); + var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)- + B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM))); + sigmaP = sigma; + sigma = s / (b*A) + deltaSigma; + } + + var tmp = sinU1*sinSigma - cosU1*cosSigma*cosAlpha1; + var lat2 = Math.atan2(sinU1*cosSigma + cosU1*sinSigma*cosAlpha1, + (1-f)*Math.sqrt(sinAlpha*sinAlpha + tmp*tmp)); + var lambda = Math.atan2(sinSigma*sinAlpha1, cosU1*cosSigma - sinU1*sinSigma*cosAlpha1); + var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha)); + var L = lambda - (1-C) * f * sinAlpha * + (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM))); + + var revAz = Math.atan2(sinAlpha, -tmp); // final bearing + + return new OpenLayers.LonLat(lon1+u.deg(L), u.deg(lat2)); +}; + +/** + * Function: getParameters + * Parse the parameters from a URL or from the current page itself into a + * JavaScript Object. Note that parameter values with commas are separated + * out into an Array. + * + * Parameters: + * url - {String} Optional url used to extract the query string. + * If url is null or is not supplied, query string is taken + * from the page location. + * options - {Object} Additional options. Optional. + * + * Valid options: + * splitArgs - {Boolean} Split comma delimited params into arrays? Default is + * true. + * + * Returns: + * {Object} An object of key/value pairs from the query string. + */ +OpenLayers.Util.getParameters = function(url, options) { + options = options || {}; + // if no url specified, take it from the location bar + url = (url === null || url === undefined) ? window.location.href : url; + + //parse out parameters portion of url string + var paramsString = ""; + if (OpenLayers.String.contains(url, '?')) { + var start = url.indexOf('?') + 1; + var end = OpenLayers.String.contains(url, "#") ? + url.indexOf('#') : url.length; + paramsString = url.substring(start, end); + } + + var parameters = {}; + var pairs = paramsString.split(/[&;]/); + for(var i=0, len=pairs.length; i 1.0) ? (1.0 / scale) + : scale; + return normScale; +}; + +/** + * Function: getResolutionFromScale + * + * Parameters: + * scale - {Float} + * units - {String} Index into OpenLayers.INCHES_PER_UNIT hashtable. + * Default is degrees + * + * Returns: + * {Float} The corresponding resolution given passed-in scale and unit + * parameters. If the given scale is falsey, the returned resolution will + * be undefined. + */ +OpenLayers.Util.getResolutionFromScale = function (scale, units) { + var resolution; + if (scale) { + if (units == null) { + units = "degrees"; + } + var normScale = OpenLayers.Util.normalizeScale(scale); + resolution = 1 / (normScale * OpenLayers.INCHES_PER_UNIT[units] + * OpenLayers.DOTS_PER_INCH); + } + return resolution; +}; + +/** + * Function: getScaleFromResolution + * + * Parameters: + * resolution - {Float} + * units - {String} Index into OpenLayers.INCHES_PER_UNIT hashtable. + * Default is degrees + * + * Returns: + * {Float} The corresponding scale given passed-in resolution and unit + * parameters. + */ +OpenLayers.Util.getScaleFromResolution = function (resolution, units) { + + if (units == null) { + units = "degrees"; + } + + var scale = resolution * OpenLayers.INCHES_PER_UNIT[units] * + OpenLayers.DOTS_PER_INCH; + return scale; +}; + +/** + * Function: pagePosition + * Calculates the position of an element on the page (see + * http://code.google.com/p/doctype/wiki/ArticlePageOffset) + * + * OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is + * Copyright (c) 2006, Yahoo! Inc. + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, with or + * without modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Yahoo! Inc. nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission of Yahoo! Inc. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Parameters: + * forElement - {DOMElement} + * + * Returns: + * {Array} two item array, Left value then Top value. + */ +OpenLayers.Util.pagePosition = function(forElement) { + // NOTE: If element is hidden (display none or disconnected or any the + // ancestors are hidden) we get (0,0) by default but we still do the + // accumulation of scroll position. + + var pos = [0, 0]; + var viewportElement = OpenLayers.Util.getViewportElement(); + if (!forElement || forElement == window || forElement == viewportElement) { + // viewport is always at 0,0 as that defined the coordinate system for + // this function - this avoids special case checks in the code below + return pos; + } + + // Gecko browsers normally use getBoxObjectFor to calculate the position. + // When invoked for an element with an implicit absolute position though it + // can be off by one. Therefore the recursive implementation is used in + // those (relatively rare) cases. + var BUGGY_GECKO_BOX_OBJECT = + OpenLayers.IS_GECKO && document.getBoxObjectFor && + OpenLayers.Element.getStyle(forElement, 'position') == 'absolute' && + (forElement.style.top == '' || forElement.style.left == ''); + + var parent = null; + var box; + + if (forElement.getBoundingClientRect) { // IE + box = forElement.getBoundingClientRect(); + var scrollTop = window.pageYOffset || viewportElement.scrollTop; + var scrollLeft = window.pageXOffset || viewportElement.scrollLeft; + + pos[0] = box.left + scrollLeft; + pos[1] = box.top + scrollTop; + + } else if (document.getBoxObjectFor && !BUGGY_GECKO_BOX_OBJECT) { // gecko + // Gecko ignores the scroll values for ancestors, up to 1.9. See: + // https://bugzilla.mozilla.org/show_bug.cgi?id=328881 and + // https://bugzilla.mozilla.org/show_bug.cgi?id=330619 + + box = document.getBoxObjectFor(forElement); + var vpBox = document.getBoxObjectFor(viewportElement); + pos[0] = box.screenX - vpBox.screenX; + pos[1] = box.screenY - vpBox.screenY; + + } else { // safari/opera + pos[0] = forElement.offsetLeft; + pos[1] = forElement.offsetTop; + parent = forElement.offsetParent; + if (parent != forElement) { + while (parent) { + pos[0] += parent.offsetLeft; + pos[1] += parent.offsetTop; + parent = parent.offsetParent; + } + } + + var browser = OpenLayers.BROWSER_NAME; + + // opera & (safari absolute) incorrectly account for body offsetTop + if (browser == "opera" || (browser == "safari" && + OpenLayers.Element.getStyle(forElement, 'position') == 'absolute')) { + pos[1] -= document.body.offsetTop; + } + + // accumulate the scroll positions for everything but the body element + parent = forElement.offsetParent; + while (parent && parent != document.body) { + pos[0] -= parent.scrollLeft; + // see https://bugs.opera.com/show_bug.cgi?id=249965 + if (browser != "opera" || parent.tagName != 'TR') { + pos[1] -= parent.scrollTop; + } + parent = parent.offsetParent; + } + } + + return pos; +}; + +/** + * Function: getViewportElement + * Returns die viewport element of the document. The viewport element is + * usually document.documentElement, except in IE,where it is either + * document.body or document.documentElement, depending on the document's + * compatibility mode (see + * http://code.google.com/p/doctype/wiki/ArticleClientViewportElement) + * + * Returns: + * {DOMElement} + */ +OpenLayers.Util.getViewportElement = function() { + var viewportElement = arguments.callee.viewportElement; + if (viewportElement == undefined) { + viewportElement = (OpenLayers.BROWSER_NAME == "msie" && + document.compatMode != 'CSS1Compat') ? document.body : + document.documentElement; + arguments.callee.viewportElement = viewportElement; + } + return viewportElement; +}; + +/** + * Function: isEquivalentUrl + * Test two URLs for equivalence. + * + * Setting 'ignoreCase' allows for case-independent comparison. + * + * Comparison is based on: + * - Protocol + * - Host (evaluated without the port) + * - Port (set 'ignorePort80' to ignore "80" values) + * - Hash ( set 'ignoreHash' to disable) + * - Pathname (for relative <-> absolute comparison) + * - Arguments (so they can be out of order) + * + * Parameters: + * url1 - {String} + * url2 - {String} + * options - {Object} Allows for customization of comparison: + * 'ignoreCase' - Default is True + * 'ignorePort80' - Default is True + * 'ignoreHash' - Default is True + * + * Returns: + * {Boolean} Whether or not the two URLs are equivalent + */ +OpenLayers.Util.isEquivalentUrl = function(url1, url2, options) { + options = options || {}; + + OpenLayers.Util.applyDefaults(options, { + ignoreCase: true, + ignorePort80: true, + ignoreHash: true, + splitArgs: false + }); + + var urlObj1 = OpenLayers.Util.createUrlObject(url1, options); + var urlObj2 = OpenLayers.Util.createUrlObject(url2, options); + + //compare all keys except for "args" (treated below) + for(var key in urlObj1) { + if(key !== "args") { + if(urlObj1[key] != urlObj2[key]) { + return false; + } + } + } + + // compare search args - irrespective of order + for(var key in urlObj1.args) { + if(urlObj1.args[key] != urlObj2.args[key]) { + return false; + } + delete urlObj2.args[key]; + } + // urlObj2 shouldn't have any args left + for(var key in urlObj2.args) { + return false; + } + + return true; +}; + +/** + * Function: createUrlObject + * + * Parameters: + * url - {String} + * options - {Object} A hash of options. + * + * Valid options: + * ignoreCase - {Boolean} lowercase url, + * ignorePort80 - {Boolean} don't include explicit port if port is 80, + * ignoreHash - {Boolean} Don't include part of url after the hash (#). + * splitArgs - {Boolean} Split comma delimited params into arrays? Default is + * true. + * + * Returns: + * {Object} An object with separate url, a, port, host, and args parsed out + * and ready for comparison + */ +OpenLayers.Util.createUrlObject = function(url, options) { + options = options || {}; + + // deal with relative urls first + if(!(/^\w+:\/\//).test(url)) { + var loc = window.location; + var port = loc.port ? ":" + loc.port : ""; + var fullUrl = loc.protocol + "//" + loc.host.split(":").shift() + port; + if(url.indexOf("/") === 0) { + // full pathname + url = fullUrl + url; + } else { + // relative to current path + var parts = loc.pathname.split("/"); + parts.pop(); + url = fullUrl + parts.join("/") + "/" + url; + } + } + + if (options.ignoreCase) { + url = url.toLowerCase(); + } + + var a = document.createElement('a'); + a.href = url; + + var urlObject = {}; + + //host (without port) + urlObject.host = a.host.split(":").shift(); + + //protocol + urlObject.protocol = a.protocol; + + //port (get uniform browser behavior with port 80 here) + if(options.ignorePort80) { + urlObject.port = (a.port == "80" || a.port == "0") ? "" : a.port; + } else { + urlObject.port = (a.port == "" || a.port == "0") ? "80" : a.port; + } + + //hash + urlObject.hash = (options.ignoreHash || a.hash === "#") ? "" : a.hash; + + //args + var queryString = a.search; + if (!queryString) { + var qMark = url.indexOf("?"); + queryString = (qMark != -1) ? url.substr(qMark) : ""; + } + urlObject.args = OpenLayers.Util.getParameters(queryString, + {splitArgs: options.splitArgs}); + + // pathname + // + // This is a workaround for Internet Explorer where + // window.location.pathname has a leading "/", but + // a.pathname has no leading "/". + urlObject.pathname = (a.pathname.charAt(0) == "/") ? a.pathname : "/" + a.pathname; + + return urlObject; +}; + +/** + * Function: removeTail + * Takes a url and removes everything after the ? and # + * + * Parameters: + * url - {String} The url to process + * + * Returns: + * {String} The string with all queryString and Hash removed + */ +OpenLayers.Util.removeTail = function(url) { + var head = null; + + var qMark = url.indexOf("?"); + var hashMark = url.indexOf("#"); + + if (qMark == -1) { + head = (hashMark != -1) ? url.substr(0,hashMark) : url; + } else { + head = (hashMark != -1) ? url.substr(0,Math.min(qMark, hashMark)) + : url.substr(0, qMark); + } + return head; +}; + +/** + * Constant: IS_GECKO + * {Boolean} True if the userAgent reports the browser to use the Gecko engine + */ +OpenLayers.IS_GECKO = (function() { + var ua = navigator.userAgent.toLowerCase(); + return ua.indexOf("webkit") == -1 && ua.indexOf("gecko") != -1; +})(); + +/** + * Constant: CANVAS_SUPPORTED + * {Boolean} True if canvas 2d is supported. + */ +OpenLayers.CANVAS_SUPPORTED = (function() { + var elem = document.createElement('canvas'); + return !!(elem.getContext && elem.getContext('2d')); +})(); + +/** + * Constant: BROWSER_NAME + * {String} + * A substring of the navigator.userAgent property. Depending on the userAgent + * property, this will be the empty string or one of the following: + * * "opera" -- Opera + * * "msie" -- Internet Explorer + * * "safari" -- Safari + * * "firefox" -- Firefox + * * "mozilla" -- Mozilla + */ +OpenLayers.BROWSER_NAME = (function() { + var name = ""; + var ua = navigator.userAgent.toLowerCase(); + if (ua.indexOf("opera") != -1) { + name = "opera"; + } else if (ua.indexOf("msie") != -1) { + name = "msie"; + } else if (ua.indexOf("safari") != -1) { + name = "safari"; + } else if (ua.indexOf("mozilla") != -1) { + if (ua.indexOf("firefox") != -1) { + name = "firefox"; + } else { + name = "mozilla"; + } + } + return name; +})(); + +/** + * Function: getBrowserName + * + * Returns: + * {String} A string which specifies which is the current + * browser in which we are running. + * + * Currently-supported browser detection and codes: + * * 'opera' -- Opera + * * 'msie' -- Internet Explorer + * * 'safari' -- Safari + * * 'firefox' -- Firefox + * * 'mozilla' -- Mozilla + * + * If we are unable to property identify the browser, we + * return an empty string. + */ +OpenLayers.Util.getBrowserName = function() { + return OpenLayers.BROWSER_NAME; +}; + +/** + * Method: getRenderedDimensions + * Renders the contentHTML offscreen to determine actual dimensions for + * popup sizing. As we need layout to determine dimensions the content + * is rendered -9999px to the left and absolute to ensure the + * scrollbars do not flicker + * + * Parameters: + * contentHTML + * size - {} If either the 'w' or 'h' properties is + * specified, we fix that dimension of the div to be measured. This is + * useful in the case where we have a limit in one dimension and must + * therefore meaure the flow in the other dimension. + * options - {Object} + * + * Allowed Options: + * displayClass - {String} Optional parameter. A CSS class name(s) string + * to provide the CSS context of the rendered content. + * containerElement - {DOMElement} Optional parameter. Insert the HTML to + * this node instead of the body root when calculating dimensions. + * + * Returns: + * {} + */ +OpenLayers.Util.getRenderedDimensions = function(contentHTML, size, options) { + + var w, h; + + // create temp container div with restricted size + var container = document.createElement("div"); + container.style.visibility = "hidden"; + + var containerElement = (options && options.containerElement) + ? options.containerElement : document.body; + + // Opera and IE7 can't handle a node with position:aboslute if it inherits + // position:absolute from a parent. + var parentHasPositionAbsolute = false; + var superContainer = null; + var parent = containerElement; + while (parent && parent.tagName.toLowerCase()!="body") { + var parentPosition = OpenLayers.Element.getStyle(parent, "position"); + if(parentPosition == "absolute") { + parentHasPositionAbsolute = true; + break; + } else if (parentPosition && parentPosition != "static") { + break; + } + parent = parent.parentNode; + } + if(parentHasPositionAbsolute && (containerElement.clientHeight === 0 || + containerElement.clientWidth === 0) ){ + superContainer = document.createElement("div"); + superContainer.style.visibility = "hidden"; + superContainer.style.position = "absolute"; + superContainer.style.overflow = "visible"; + superContainer.style.width = document.body.clientWidth + "px"; + superContainer.style.height = document.body.clientHeight + "px"; + superContainer.appendChild(container); + } + container.style.position = "absolute"; + + //fix a dimension, if specified. + if (size) { + if (size.w) { + w = size.w; + container.style.width = w + "px"; + } else if (size.h) { + h = size.h; + container.style.height = h + "px"; + } + } + + //add css classes, if specified + if (options && options.displayClass) { + container.className = options.displayClass; + } + + // create temp content div and assign content + var content = document.createElement("div"); + content.innerHTML = contentHTML; + + // we need overflow visible when calculating the size + content.style.overflow = "visible"; + if (content.childNodes) { + for (var i=0, l=content.childNodes.length; i= 60) { + coordinateseconds -= 60; + coordinateminutes += 1; + if( coordinateminutes >= 60) { + coordinateminutes -= 60; + coordinatedegrees += 1; + } + } + + if( coordinatedegrees < 10 ) { + coordinatedegrees = "0" + coordinatedegrees; + } + var str = coordinatedegrees + "\u00B0"; + + if (dmsOption.indexOf('dm') >= 0) { + if( coordinateminutes < 10 ) { + coordinateminutes = "0" + coordinateminutes; + } + str += coordinateminutes + "'"; + + if (dmsOption.indexOf('dms') >= 0) { + if( coordinateseconds < 10 ) { + coordinateseconds = "0" + coordinateseconds; + } + str += coordinateseconds + '"'; + } + } + + if (axis == "lon") { + str += coordinate < 0 ? OpenLayers.i18n("W") : OpenLayers.i18n("E"); + } else { + str += coordinate < 0 ? OpenLayers.i18n("S") : OpenLayers.i18n("N"); + } + return str; +}; + +/* ====================================================================== + OpenLayers/Events.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Util.js + */ + +/** + * Namespace: OpenLayers.Event + * Utility functions for event handling. + */ +OpenLayers.Event = { + + /** + * Property: observers + * {Object} A hashtable cache of the event observers. Keyed by + * element._eventCacheID + */ + observers: false, + + /** + * Constant: KEY_SPACE + * {int} + */ + KEY_SPACE: 32, + + /** + * Constant: KEY_BACKSPACE + * {int} + */ + KEY_BACKSPACE: 8, + + /** + * Constant: KEY_TAB + * {int} + */ + KEY_TAB: 9, + + /** + * Constant: KEY_RETURN + * {int} + */ + KEY_RETURN: 13, + + /** + * Constant: KEY_ESC + * {int} + */ + KEY_ESC: 27, + + /** + * Constant: KEY_LEFT + * {int} + */ + KEY_LEFT: 37, + + /** + * Constant: KEY_UP + * {int} + */ + KEY_UP: 38, + + /** + * Constant: KEY_RIGHT + * {int} + */ + KEY_RIGHT: 39, + + /** + * Constant: KEY_DOWN + * {int} + */ + KEY_DOWN: 40, + + /** + * Constant: KEY_DELETE + * {int} + */ + KEY_DELETE: 46, + + + /** + * Method: element + * Cross browser event element detection. + * + * Parameters: + * event - {Event} + * + * Returns: + * {DOMElement} The element that caused the event + */ + element: function(event) { + return event.target || event.srcElement; + }, + + /** + * Method: isSingleTouch + * Determine whether event was caused by a single touch + * + * Parameters: + * event - {Event} + * + * Returns: + * {Boolean} + */ + isSingleTouch: function(event) { + return event.touches && event.touches.length == 1; + }, + + /** + * Method: isMultiTouch + * Determine whether event was caused by a multi touch + * + * Parameters: + * event - {Event} + * + * Returns: + * {Boolean} + */ + isMultiTouch: function(event) { + return event.touches && event.touches.length > 1; + }, + + /** + * Method: isLeftClick + * Determine whether event was caused by a left click. + * + * Parameters: + * event - {Event} + * + * Returns: + * {Boolean} + */ + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); + }, + + /** + * Method: isRightClick + * Determine whether event was caused by a right mouse click. + * + * Parameters: + * event - {Event} + * + * Returns: + * {Boolean} + */ + isRightClick: function(event) { + return (((event.which) && (event.which == 3)) || + ((event.button) && (event.button == 2))); + }, + + /** + * Method: stop + * Stops an event from propagating. + * + * Parameters: + * event - {Event} + * allowDefault - {Boolean} If true, we stop the event chain but + * still allow the default browser behaviour (text selection, + * radio-button clicking, etc). Default is false. + */ + stop: function(event, allowDefault) { + + if (!allowDefault) { + OpenLayers.Event.preventDefault(event); + } + + if (event.stopPropagation) { + event.stopPropagation(); + } else { + event.cancelBubble = true; + } + }, + + /** + * Method: preventDefault + * Cancels the event if it is cancelable, without stopping further + * propagation of the event. + * + * Parameters: + * event - {Event} + */ + preventDefault: function(event) { + if (event.preventDefault) { + event.preventDefault(); + } else { + event.returnValue = false; + } + }, + + /** + * Method: findElement + * + * Parameters: + * event - {Event} + * tagName - {String} + * + * Returns: + * {DOMElement} The first node with the given tagName, starting from the + * node the event was triggered on and traversing the DOM upwards + */ + findElement: function(event, tagName) { + var element = OpenLayers.Event.element(event); + while (element.parentNode && (!element.tagName || + (element.tagName.toUpperCase() != tagName.toUpperCase()))){ + element = element.parentNode; + } + return element; + }, + + /** + * Method: observe + * + * Parameters: + * elementParam - {DOMElement || String} + * name - {String} + * observer - {function} + * useCapture - {Boolean} + */ + observe: function(elementParam, name, observer, useCapture) { + var element = OpenLayers.Util.getElement(elementParam); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.attachEvent)) { + name = 'keydown'; + } + + //if observers cache has not yet been created, create it + if (!this.observers) { + this.observers = {}; + } + + //if not already assigned, make a new unique cache ID + if (!element._eventCacheID) { + var idPrefix = "eventCacheID_"; + if (element.id) { + idPrefix = element.id + "_" + idPrefix; + } + element._eventCacheID = OpenLayers.Util.createUniqueID(idPrefix); + } + + var cacheID = element._eventCacheID; + + //if there is not yet a hash entry for this element, add one + if (!this.observers[cacheID]) { + this.observers[cacheID] = []; + } + + //add a new observer to this element's list + this.observers[cacheID].push({ + 'element': element, + 'name': name, + 'observer': observer, + 'useCapture': useCapture + }); + + //add the actual browser event listener + if (element.addEventListener) { + element.addEventListener(name, observer, useCapture); + } else if (element.attachEvent) { + element.attachEvent('on' + name, observer); + } + }, + + /** + * Method: stopObservingElement + * Given the id of an element to stop observing, cycle through the + * element's cached observers, calling stopObserving on each one, + * skipping those entries which can no longer be removed. + * + * parameters: + * elementParam - {DOMElement || String} + */ + stopObservingElement: function(elementParam) { + var element = OpenLayers.Util.getElement(elementParam); + var cacheID = element._eventCacheID; + + this._removeElementObservers(OpenLayers.Event.observers[cacheID]); + }, + + /** + * Method: _removeElementObservers + * + * Parameters: + * elementObservers - {Array(Object)} Array of (element, name, + * observer, usecapture) objects, + * taken directly from hashtable + */ + _removeElementObservers: function(elementObservers) { + if (elementObservers) { + for(var i = elementObservers.length-1; i >= 0; i--) { + var entry = elementObservers[i]; + OpenLayers.Event.stopObserving.apply(this, [ + entry.element, entry.name, entry.observer, entry.useCapture + ]); + } + } + }, + + /** + * Method: stopObserving + * + * Parameters: + * elementParam - {DOMElement || String} + * name - {String} + * observer - {function} + * useCapture - {Boolean} + * + * Returns: + * {Boolean} Whether or not the event observer was removed + */ + stopObserving: function(elementParam, name, observer, useCapture) { + useCapture = useCapture || false; + + var element = OpenLayers.Util.getElement(elementParam); + var cacheID = element._eventCacheID; + + if (name == 'keypress') { + if ( navigator.appVersion.match(/Konqueror|Safari|KHTML/) || + element.detachEvent) { + name = 'keydown'; + } + } + + // find element's entry in this.observers cache and remove it + var foundEntry = false; + var elementObservers = OpenLayers.Event.observers[cacheID]; + if (elementObservers) { + + // find the specific event type in the element's list + var i=0; + while(!foundEntry && i < elementObservers.length) { + var cacheEntry = elementObservers[i]; + + if ((cacheEntry.name == name) && + (cacheEntry.observer == observer) && + (cacheEntry.useCapture == useCapture)) { + + elementObservers.splice(i, 1); + if (elementObservers.length == 0) { + delete OpenLayers.Event.observers[cacheID]; + } + foundEntry = true; + break; + } + i++; + } + } + + //actually remove the event listener from browser + if (foundEntry) { + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element && element.detachEvent) { + element.detachEvent('on' + name, observer); + } + } + return foundEntry; + }, + + /** + * Method: unloadCache + * Cycle through all the element entries in the events cache and call + * stopObservingElement on each. + */ + unloadCache: function() { + // check for OpenLayers.Event before checking for observers, because + // OpenLayers.Event may be undefined in IE if no map instance was + // created + if (OpenLayers.Event && OpenLayers.Event.observers) { + for (var cacheID in OpenLayers.Event.observers) { + var elementObservers = OpenLayers.Event.observers[cacheID]; + OpenLayers.Event._removeElementObservers.apply(this, + [elementObservers]); + } + OpenLayers.Event.observers = false; + } + }, + + CLASS_NAME: "OpenLayers.Event" +}; + +/* prevent memory leaks in IE */ +OpenLayers.Event.observe(window, 'unload', OpenLayers.Event.unloadCache, false); + +/** + * Class: OpenLayers.Events + */ +OpenLayers.Events = OpenLayers.Class({ + + /** + * Constant: BROWSER_EVENTS + * {Array(String)} supported events + */ + BROWSER_EVENTS: [ + "mouseover", "mouseout", + "mousedown", "mouseup", "mousemove", + "click", "dblclick", "rightclick", "dblrightclick", + "resize", "focus", "blur", + "touchstart", "touchmove", "touchend", + "keydown" + ], + + /** + * Property: listeners + * {Object} Hashtable of Array(Function): events listener functions + */ + listeners: null, + + /** + * Property: object + * {Object} the code object issuing application events + */ + object: null, + + /** + * Property: element + * {DOMElement} the DOM element receiving browser events + */ + element: null, + + /** + * Property: eventHandler + * {Function} bound event handler attached to elements + */ + eventHandler: null, + + /** + * APIProperty: fallThrough + * {Boolean} + */ + fallThrough: null, + + /** + * APIProperty: includeXY + * {Boolean} Should the .xy property automatically be created for browser + * mouse events? In general, this should be false. If it is true, then + * mouse events will automatically generate a '.xy' property on the + * event object that is passed. (Prior to OpenLayers 2.7, this was true + * by default.) Otherwise, you can call the getMousePosition on the + * relevant events handler on the object available via the 'evt.object' + * property of the evt object. So, for most events, you can call: + * function named(evt) { + * this.xy = this.object.events.getMousePosition(evt) + * } + * + * This option typically defaults to false for performance reasons: + * when creating an events object whose primary purpose is to manage + * relatively positioned mouse events within a div, it may make + * sense to set it to true. + * + * This option is also used to control whether the events object caches + * offsets. If this is false, it will not: the reason for this is that + * it is only expected to be called many times if the includeXY property + * is set to true. If you set this to true, you are expected to clear + * the offset cache manually (using this.clearMouseCache()) if: + * the border of the element changes + * the location of the element in the page changes + */ + includeXY: false, + + /** + * APIProperty: extensions + * {Object} Event extensions registered with this instance. Keys are + * event types, values are {OpenLayers.Events.*} extension instances or + * {Boolean} for events that an instantiated extension provides in + * addition to the one it was created for. + * + * Extensions create an event in addition to browser events, which usually + * fires when a sequence of browser events is completed. Extensions are + * automatically instantiated when a listener is registered for an event + * provided by an extension. + * + * Extensions are created in the namespace using + * , and named after the event they provide. + * The constructor receives the target instance as + * argument. Extensions that need to capture browser events before they + * propagate can register their listeners events using , with + * {extension: true} as 4th argument. + * + * If an extension creates more than one event, an alias for each event + * type should be created and reference the same class. The constructor + * should set a reference in the target's extensions registry to itself. + * + * Below is a minimal extension that provides the "foostart" and "fooend" + * event types, which replace the native "click" event type if clicked on + * an element with the css class "foo": + * + * (code) + * OpenLayers.Events.foostart = OpenLayers.Class({ + * initialize: function(target) { + * this.target = target; + * this.target.register("click", this, this.doStuff, {extension: true}); + * // only required if extension provides more than one event type + * this.target.extensions["foostart"] = true; + * this.target.extensions["fooend"] = true; + * }, + * destroy: function() { + * var target = this.target; + * target.unregister("click", this, this.doStuff); + * delete this.target; + * // only required if extension provides more than one event type + * delete target.extensions["foostart"]; + * delete target.extensions["fooend"]; + * }, + * doStuff: function(evt) { + * var propagate = true; + * if (OpenLayers.Event.element(evt).className === "foo") { + * propagate = false; + * var target = this.target; + * target.triggerEvent("foostart"); + * window.setTimeout(function() { + * target.triggerEvent("fooend"); + * }, 1000); + * } + * return propagate; + * } + * }); + * // only required if extension provides more than one event type + * OpenLayers.Events.fooend = OpenLayers.Events.foostart; + * (end) + * + */ + extensions: null, + + /** + * Property: extensionCount + * {Object} Keys are event types (like in ), values are the + * number of extension listeners for each event type. + */ + extensionCount: null, + + /** + * Method: clearMouseListener + * A version of that is bound to this instance so that + * it can be used with and + * . + */ + clearMouseListener: null, + + /** + * Constructor: OpenLayers.Events + * Construct an OpenLayers.Events object. + * + * Parameters: + * object - {Object} The js object to which this Events object is being added + * element - {DOMElement} A dom element to respond to browser events + * eventTypes - {Array(String)} Deprecated. Array of custom application + * events. A listener may be registered for any named event, regardless + * of the values provided here. + * fallThrough - {Boolean} Allow events to fall through after these have + * been handled? + * options - {Object} Options for the events object. + */ + initialize: function (object, element, eventTypes, fallThrough, options) { + OpenLayers.Util.extend(this, options); + this.object = object; + this.fallThrough = fallThrough; + this.listeners = {}; + this.extensions = {}; + this.extensionCount = {}; + this._msTouches = []; + + // if a dom element is specified, add a listeners list + // for browser events on the element and register them + if (element != null) { + this.attachToElement(element); + } + }, + + /** + * APIMethod: destroy + */ + destroy: function () { + for (var e in this.extensions) { + if (typeof this.extensions[e] !== "boolean") { + this.extensions[e].destroy(); + } + } + this.extensions = null; + if (this.element) { + OpenLayers.Event.stopObservingElement(this.element); + if(this.element.hasScrollEvent) { + OpenLayers.Event.stopObserving( + window, "scroll", this.clearMouseListener + ); + } + } + this.element = null; + + this.listeners = null; + this.object = null; + this.fallThrough = null; + this.eventHandler = null; + }, + + /** + * APIMethod: addEventType + * Deprecated. Any event can be triggered without adding it first. + * + * Parameters: + * eventName - {String} + */ + addEventType: function(eventName) { + }, + + /** + * Method: attachToElement + * + * Parameters: + * element - {HTMLDOMElement} a DOM element to attach browser events to + */ + attachToElement: function (element) { + if (this.element) { + OpenLayers.Event.stopObservingElement(this.element); + } else { + // keep a bound copy of handleBrowserEvent() so that we can + // pass the same function to both Event.observe() and .stopObserving() + this.eventHandler = OpenLayers.Function.bindAsEventListener( + this.handleBrowserEvent, this + ); + + // to be used with observe and stopObserving + this.clearMouseListener = OpenLayers.Function.bind( + this.clearMouseCache, this + ); + } + this.element = element; + var msTouch = !!window.navigator.msMaxTouchPoints; + var type; + for (var i = 0, len = this.BROWSER_EVENTS.length; i < len; i++) { + type = this.BROWSER_EVENTS[i]; + // register the event cross-browser + OpenLayers.Event.observe(element, type, this.eventHandler + ); + if (msTouch && type.indexOf('touch') === 0) { + this.addMsTouchListener(element, type, this.eventHandler); + } + } + // disable dragstart in IE so that mousedown/move/up works normally + OpenLayers.Event.observe(element, "dragstart", OpenLayers.Event.stop); + }, + + /** + * APIMethod: on + * Convenience method for registering listeners with a common scope. + * Internally, this method calls as shown in the examples + * below. + * + * Example use: + * (code) + * // register a single listener for the "loadstart" event + * events.on({"loadstart": loadStartListener}); + * + * // this is equivalent to the following + * events.register("loadstart", undefined, loadStartListener); + * + * // register multiple listeners to be called with the same `this` object + * events.on({ + * "loadstart": loadStartListener, + * "loadend": loadEndListener, + * scope: object + * }); + * + * // this is equivalent to the following + * events.register("loadstart", object, loadStartListener); + * events.register("loadend", object, loadEndListener); + * (end) + * + * Parameters: + * object - {Object} + */ + on: function(object) { + for(var type in object) { + if(type != "scope" && object.hasOwnProperty(type)) { + this.register(type, object.scope, object[type]); + } + } + }, + + /** + * APIMethod: register + * Register an event on the events object. + * + * When the event is triggered, the 'func' function will be called, in the + * context of 'obj'. Imagine we were to register an event, specifying an + * OpenLayers.Bounds Object as 'obj'. When the event is triggered, the + * context in the callback function will be our Bounds object. This means + * that within our callback function, we can access the properties and + * methods of the Bounds object through the "this" variable. So our + * callback could execute something like: + * : leftStr = "Left: " + this.left; + * + * or + * + * : centerStr = "Center: " + this.getCenterLonLat(); + * + * Parameters: + * type - {String} Name of the event to register + * obj - {Object} The object to bind the context to for the callback#. + * If no object is specified, default is the Events's 'object' property. + * func - {Function} The callback function. If no callback is + * specified, this function does nothing. + * priority - {Boolean|Object} If true, adds the new listener to the + * *front* of the events queue instead of to the end. + * + * Valid options for priority: + * extension - {Boolean} If true, then the event will be registered as + * extension event. Extension events are handled before all other + * events. + */ + register: function (type, obj, func, priority) { + if (type in OpenLayers.Events && !this.extensions[type]) { + this.extensions[type] = new OpenLayers.Events[type](this); + } + if (func != null) { + if (obj == null) { + obj = this.object; + } + var listeners = this.listeners[type]; + if (!listeners) { + listeners = []; + this.listeners[type] = listeners; + this.extensionCount[type] = 0; + } + var listener = {obj: obj, func: func}; + if (priority) { + listeners.splice(this.extensionCount[type], 0, listener); + if (typeof priority === "object" && priority.extension) { + this.extensionCount[type]++; + } + } else { + listeners.push(listener); + } + } + }, + + /** + * APIMethod: registerPriority + * Same as register() but adds the new listener to the *front* of the + * events queue instead of to the end. + * + * TODO: get rid of this in 3.0 - Decide whether listeners should be + * called in the order they were registered or in reverse order. + * + * + * Parameters: + * type - {String} Name of the event to register + * obj - {Object} The object to bind the context to for the callback#. + * If no object is specified, default is the Events's + * 'object' property. + * func - {Function} The callback function. If no callback is + * specified, this function does nothing. + */ + registerPriority: function (type, obj, func) { + this.register(type, obj, func, true); + }, + + /** + * APIMethod: un + * Convenience method for unregistering listeners with a common scope. + * Internally, this method calls as shown in the examples + * below. + * + * Example use: + * (code) + * // unregister a single listener for the "loadstart" event + * events.un({"loadstart": loadStartListener}); + * + * // this is equivalent to the following + * events.unregister("loadstart", undefined, loadStartListener); + * + * // unregister multiple listeners with the same `this` object + * events.un({ + * "loadstart": loadStartListener, + * "loadend": loadEndListener, + * scope: object + * }); + * + * // this is equivalent to the following + * events.unregister("loadstart", object, loadStartListener); + * events.unregister("loadend", object, loadEndListener); + * (end) + */ + un: function(object) { + for(var type in object) { + if(type != "scope" && object.hasOwnProperty(type)) { + this.unregister(type, object.scope, object[type]); + } + } + }, + + /** + * APIMethod: unregister + * + * Parameters: + * type - {String} + * obj - {Object} If none specified, defaults to this.object + * func - {Function} + */ + unregister: function (type, obj, func) { + if (obj == null) { + obj = this.object; + } + var listeners = this.listeners[type]; + if (listeners != null) { + for (var i=0, len=listeners.length; i Math.floor(evt.pageY) || + evt.pageX === 0 && Math.floor(x) > Math.floor(evt.pageX)) { + // iOS4 include scroll offset in clientX/Y + x = x - winPageX; + y = y - winPageY; + } else if (y < (evt.pageY - winPageY) || x < (evt.pageX - winPageX) ) { + // Some Android browsers have totally bogus values for clientX/Y + // when scrolling/zooming a page + x = evt.pageX - winPageX; + y = evt.pageY - winPageY; + } + + evt.olClientX = x; + evt.olClientY = y; + + return { + clientX: x, + clientY: y + }; + }, + + /** + * APIMethod: clearMouseCache + * Clear cached data about the mouse position. This should be called any + * time the element that events are registered on changes position + * within the page. + */ + clearMouseCache: function() { + this.element.scrolls = null; + this.element.lefttop = null; + this.element.offsets = null; + }, + + /** + * Method: getMousePosition + * + * Parameters: + * evt - {Event} + * + * Returns: + * {} The current xy coordinate of the mouse, adjusted + * for offsets + */ + getMousePosition: function (evt) { + if (!this.includeXY) { + this.clearMouseCache(); + } else if (!this.element.hasScrollEvent) { + OpenLayers.Event.observe(window, "scroll", this.clearMouseListener); + this.element.hasScrollEvent = true; + } + + if (!this.element.scrolls) { + var viewportElement = OpenLayers.Util.getViewportElement(); + this.element.scrolls = [ + window.pageXOffset || viewportElement.scrollLeft, + window.pageYOffset || viewportElement.scrollTop + ]; + } + + if (!this.element.lefttop) { + this.element.lefttop = [ + (document.documentElement.clientLeft || 0), + (document.documentElement.clientTop || 0) + ]; + } + + if (!this.element.offsets) { + this.element.offsets = OpenLayers.Util.pagePosition(this.element); + } + + return new OpenLayers.Pixel( + (evt.clientX + this.element.scrolls[0]) - this.element.offsets[0] + - this.element.lefttop[0], + (evt.clientY + this.element.scrolls[1]) - this.element.offsets[1] + - this.element.lefttop[1] + ); + }, + + /** + * Method: addMsTouchListener + * + * Parameters: + * element - {DOMElement} The DOM element to register the listener on + * type - {String} The event type + * handler - {Function} the handler + */ + addMsTouchListener: function (element, type, handler) { + var eventHandler = this.eventHandler; + var touches = this._msTouches; + + function msHandler(evt) { + handler(OpenLayers.Util.applyDefaults({ + stopPropagation: function() { + for (var i=touches.length-1; i>=0; --i) { + touches[i].stopPropagation(); + } + }, + preventDefault: function() { + for (var i=touches.length-1; i>=0; --i) { + touches[i].preventDefault(); + } + }, + type: type + }, evt)); + } + + switch (type) { + case 'touchstart': + return this.addMsTouchListenerStart(element, type, msHandler); + case 'touchend': + return this.addMsTouchListenerEnd(element, type, msHandler); + case 'touchmove': + return this.addMsTouchListenerMove(element, type, msHandler); + default: + throw 'Unknown touch event type'; + } + }, + + /** + * Method: addMsTouchListenerStart + * + * Parameters: + * element - {DOMElement} The DOM element to register the listener on + * type - {String} The event type + * handler - {Function} the handler + */ + addMsTouchListenerStart: function(element, type, handler) { + var touches = this._msTouches; + + var cb = function(e) { + + var alreadyInArray = false; + for (var i=0, ii=touches.length; i when a button was + * clicked. Buttons are detected by the "olButton" class. + * + * This event type makes sure that button clicks do not interfere with other + * events that are registered on the same . + * + * Event types provided by this extension: + * - *buttonclick* Triggered when a button is clicked. Listeners receive an + * object with a *buttonElement* property referencing the dom element of + * the clicked button, and an *buttonXY* property with the click position + * relative to the button. + */ +OpenLayers.Events.buttonclick = OpenLayers.Class({ + + /** + * Property: target + * {} The events instance that the buttonclick event will + * be triggered on. + */ + target: null, + + /** + * Property: events + * {Array} Events to observe and conditionally stop from propagating when + * an element with the olButton class (or its olAlphaImg child) is + * clicked. + */ + events: [ + 'mousedown', 'mouseup', 'click', 'dblclick', + 'touchstart', 'touchmove', 'touchend', 'keydown' + ], + + /** + * Property: startRegEx + * {RegExp} Regular expression to test Event.type for events that start + * a buttonclick sequence. + */ + startRegEx: /^mousedown|touchstart$/, + + /** + * Property: cancelRegEx + * {RegExp} Regular expression to test Event.type for events that cancel + * a buttonclick sequence. + */ + cancelRegEx: /^touchmove$/, + + /** + * Property: completeRegEx + * {RegExp} Regular expression to test Event.type for events that complete + * a buttonclick sequence. + */ + completeRegEx: /^mouseup|touchend$/, + + /** + * Property: startEvt + * {Event} The event that started the click sequence + */ + + /** + * Constructor: OpenLayers.Events.buttonclick + * Construct a buttonclick event type. Applications are not supposed to + * create instances of this class - they are created on demand by + * instances. + * + * Parameters: + * target - {} The events instance that the buttonclick + * event will be triggered on. + */ + initialize: function(target) { + this.target = target; + for (var i=this.events.length-1; i>=0; --i) { + this.target.register(this.events[i], this, this.buttonClick, { + extension: true + }); + } + }, + + /** + * Method: destroy + */ + destroy: function() { + for (var i=this.events.length-1; i>=0; --i) { + this.target.unregister(this.events[i], this, this.buttonClick); + } + delete this.target; + }, + + /** + * Method: getPressedButton + * Get the pressed button, if any. Returns undefined if no button + * was pressed. + * + * Arguments: + * element - {DOMElement} The event target. + * + * Returns: + * {DOMElement} The button element, or undefined. + */ + getPressedButton: function(element) { + var depth = 3, // limit the search depth + button; + do { + if(OpenLayers.Element.hasClass(element, "olButton")) { + // hit! + button = element; + break; + } + element = element.parentNode; + } while(--depth > 0 && element); + return button; + }, + + /** + * Method: ignore + * Check for event target elements that should be ignored by OpenLayers. + * + * Parameters: + * element - {DOMElement} The event target. + */ + ignore: function(element) { + var depth = 3, + ignore = false; + do { + if (element.nodeName.toLowerCase() === 'a') { + ignore = true; + break; + } + element = element.parentNode; + } while (--depth > 0 && element); + return ignore; + }, + + /** + * Method: buttonClick + * Check if a button was clicked, and fire the buttonclick event + * + * Parameters: + * evt - {Event} + */ + buttonClick: function(evt) { + var propagate = true, + element = OpenLayers.Event.element(evt); + if (element && (OpenLayers.Event.isLeftClick(evt) || !~evt.type.indexOf("mouse"))) { + // was a button pressed? + var button = this.getPressedButton(element); + if (button) { + if (evt.type === "keydown") { + switch (evt.keyCode) { + case OpenLayers.Event.KEY_RETURN: + case OpenLayers.Event.KEY_SPACE: + this.target.triggerEvent("buttonclick", { + buttonElement: button + }); + OpenLayers.Event.stop(evt); + propagate = false; + break; + } + } else if (this.startEvt) { + if (this.completeRegEx.test(evt.type)) { + var pos = OpenLayers.Util.pagePosition(button); + var viewportElement = OpenLayers.Util.getViewportElement(); + var scrollTop = window.pageYOffset || viewportElement.scrollTop; + var scrollLeft = window.pageXOffset || viewportElement.scrollLeft; + pos[0] = pos[0] - scrollLeft; + pos[1] = pos[1] - scrollTop; + + this.target.triggerEvent("buttonclick", { + buttonElement: button, + buttonXY: { + x: this.startEvt.clientX - pos[0], + y: this.startEvt.clientY - pos[1] + } + }); + } + if (this.cancelRegEx.test(evt.type)) { + delete this.startEvt; + } + OpenLayers.Event.stop(evt); + propagate = false; + } + if (this.startRegEx.test(evt.type)) { + this.startEvt = evt; + OpenLayers.Event.stop(evt); + propagate = false; + } + } else { + propagate = !this.ignore(OpenLayers.Event.element(evt)); + delete this.startEvt; + } + } + return propagate; + } + +}); +/* ====================================================================== + OpenLayers/Control.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/BaseTypes/Class.js + */ + +/** + * Class: OpenLayers.Control + * Controls affect the display or behavior of the map. They allow everything + * from panning and zooming to displaying a scale indicator. Controls by + * default are added to the map they are contained within however it is + * possible to add a control to an external div by passing the div in the + * options parameter. + * + * Example: + * The following example shows how to add many of the common controls + * to a map. + * + * > var map = new OpenLayers.Map('map', { controls: [] }); + * > + * > map.addControl(new OpenLayers.Control.PanZoomBar()); + * > map.addControl(new OpenLayers.Control.LayerSwitcher({'ascending':false})); + * > map.addControl(new OpenLayers.Control.Permalink()); + * > map.addControl(new OpenLayers.Control.Permalink('permalink')); + * > map.addControl(new OpenLayers.Control.MousePosition()); + * > map.addControl(new OpenLayers.Control.OverviewMap()); + * > map.addControl(new OpenLayers.Control.KeyboardDefaults()); + * + * The next code fragment is a quick example of how to intercept + * shift-mouse click to display the extent of the bounding box + * dragged out by the user. Usually controls are not created + * in exactly this manner. See the source for a more complete + * example: + * + * > var control = new OpenLayers.Control(); + * > OpenLayers.Util.extend(control, { + * > draw: function () { + * > // this Handler.Box will intercept the shift-mousedown + * > // before Control.MouseDefault gets to see it + * > this.box = new OpenLayers.Handler.Box( control, + * > {"done": this.notice}, + * > {keyMask: OpenLayers.Handler.MOD_SHIFT}); + * > this.box.activate(); + * > }, + * > + * > notice: function (bounds) { + * > OpenLayers.Console.userError(bounds); + * > } + * > }); + * > map.addControl(control); + * + */ +OpenLayers.Control = OpenLayers.Class({ + + /** + * Property: id + * {String} + */ + id: null, + + /** + * Property: map + * {} this gets set in the addControl() function in + * OpenLayers.Map + */ + map: null, + + /** + * APIProperty: div + * {DOMElement} The element that contains the control, if not present the + * control is placed inside the map. + */ + div: null, + + /** + * APIProperty: type + * {Number} Controls can have a 'type'. The type determines the type of + * interactions which are possible with them when they are placed in an + * . + */ + type: null, + + /** + * Property: allowSelection + * {Boolean} By default, controls do not allow selection, because + * it may interfere with map dragging. If this is true, OpenLayers + * will not prevent selection of the control. + * Default is false. + */ + allowSelection: false, + + /** + * Property: displayClass + * {string} This property is used for CSS related to the drawing of the + * Control. + */ + displayClass: "", + + /** + * APIProperty: title + * {string} This property is used for showing a tooltip over the + * Control. + */ + title: "", + + /** + * APIProperty: autoActivate + * {Boolean} Activate the control when it is added to a map. Default is + * false. + */ + autoActivate: false, + + /** + * APIProperty: active + * {Boolean} The control is active (read-only). Use and + * to change control state. + */ + active: null, + + /** + * Property: handlerOptions + * {Object} Used to set non-default properties on the control's handler + */ + handlerOptions: null, + + /** + * Property: handler + * {} null + */ + handler: null, + + /** + * APIProperty: eventListeners + * {Object} If set as an option at construction, the eventListeners + * object will be registered with . Object + * structure must be a listeners object as shown in the example for + * the events.on method. + */ + eventListeners: null, + + /** + * APIProperty: events + * {} Events instance for listeners and triggering + * control specific events. + * + * Register a listener for a particular event with the following syntax: + * (code) + * control.events.register(type, obj, listener); + * (end) + * + * Listeners will be called with a reference to an event object. The + * properties of this event depends on exactly what happened. + * + * All event objects have at least the following properties: + * object - {Object} A reference to control.events.object (a reference + * to the control). + * element - {DOMElement} A reference to control.events.element (which + * will be null unless documented otherwise). + * + * Supported map event types: + * activate - Triggered when activated. + * deactivate - Triggered when deactivated. + */ + events: null, + + /** + * Constructor: OpenLayers.Control + * Create an OpenLayers Control. The options passed as a parameter + * directly extend the control. For example passing the following: + * + * > var control = new OpenLayers.Control({div: myDiv}); + * + * Overrides the default div attribute value of null. + * + * Parameters: + * options - {Object} + */ + initialize: function (options) { + // We do this before the extend so that instances can override + // className in options. + this.displayClass = + this.CLASS_NAME.replace("OpenLayers.", "ol").replace(/\./g, ""); + + OpenLayers.Util.extend(this, options); + + this.events = new OpenLayers.Events(this); + if(this.eventListeners instanceof Object) { + this.events.on(this.eventListeners); + } + if (this.id == null) { + this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_"); + } + }, + + /** + * Method: destroy + * The destroy method is used to perform any clean up before the control + * is dereferenced. Typically this is where event listeners are removed + * to prevent memory leaks. + */ + destroy: function () { + if(this.events) { + if(this.eventListeners) { + this.events.un(this.eventListeners); + } + this.events.destroy(); + this.events = null; + } + this.eventListeners = null; + + // eliminate circular references + if (this.handler) { + this.handler.destroy(); + this.handler = null; + } + if(this.handlers) { + for(var key in this.handlers) { + if(this.handlers.hasOwnProperty(key) && + typeof this.handlers[key].destroy == "function") { + this.handlers[key].destroy(); + } + } + this.handlers = null; + } + if (this.map) { + this.map.removeControl(this); + this.map = null; + } + this.div = null; + }, + + /** + * Method: setMap + * Set the map property for the control. This is done through an accessor + * so that subclasses can override this and take special action once + * they have their map variable set. + * + * Parameters: + * map - {} + */ + setMap: function(map) { + this.map = map; + if (this.handler) { + this.handler.setMap(map); + } + }, + + /** + * Method: draw + * The draw method is called when the control is ready to be displayed + * on the page. If a div has not been created one is created. Controls + * with a visual component will almost always want to override this method + * to customize the look of control. + * + * Parameters: + * px - {} The top-left pixel position of the control + * or null. + * + * Returns: + * {DOMElement} A reference to the DIV DOMElement containing the control + */ + draw: function (px) { + if (this.div == null) { + this.div = OpenLayers.Util.createDiv(this.id); + this.div.className = this.displayClass; + if (!this.allowSelection) { + this.div.className += " olControlNoSelect"; + this.div.setAttribute("unselectable", "on", 0); + this.div.onselectstart = OpenLayers.Function.False; + } + if (this.title != "") { + this.div.title = this.title; + } + } + if (px != null) { + this.position = px.clone(); + } + this.moveTo(this.position); + return this.div; + }, + + /** + * Method: moveTo + * Sets the left and top style attributes to the passed in pixel + * coordinates. + * + * Parameters: + * px - {} + */ + moveTo: function (px) { + if ((px != null) && (this.div != null)) { + this.div.style.left = px.x + "px"; + this.div.style.top = px.y + "px"; + } + }, + + /** + * APIMethod: activate + * Explicitly activates a control and it's associated + * handler if one has been set. Controls can be + * deactivated by calling the deactivate() method. + * + * Returns: + * {Boolean} True if the control was successfully activated or + * false if the control was already active. + */ + activate: function () { + if (this.active) { + return false; + } + if (this.handler) { + this.handler.activate(); + } + this.active = true; + if(this.map) { + OpenLayers.Element.addClass( + this.map.viewPortDiv, + this.displayClass.replace(/ /g, "") + "Active" + ); + } + this.events.triggerEvent("activate"); + return true; + }, + + /** + * APIMethod: deactivate + * Deactivates a control and it's associated handler if any. The exact + * effect of this depends on the control itself. + * + * Returns: + * {Boolean} True if the control was effectively deactivated or false + * if the control was already inactive. + */ + deactivate: function () { + if (this.active) { + if (this.handler) { + this.handler.deactivate(); + } + this.active = false; + if(this.map) { + OpenLayers.Element.removeClass( + this.map.viewPortDiv, + this.displayClass.replace(/ /g, "") + "Active" + ); + } + this.events.triggerEvent("deactivate"); + return true; + } + return false; + }, + + CLASS_NAME: "OpenLayers.Control" +}); + +/** + * Constant: OpenLayers.Control.TYPE_BUTTON + */ +OpenLayers.Control.TYPE_BUTTON = 1; + +/** + * Constant: OpenLayers.Control.TYPE_TOGGLE + */ +OpenLayers.Control.TYPE_TOGGLE = 2; + +/** + * Constant: OpenLayers.Control.TYPE_TOOL + */ +OpenLayers.Control.TYPE_TOOL = 3; +/* ====================================================================== + OpenLayers/Geometry.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/BaseTypes/Class.js + */ + +/** + * Class: OpenLayers.Geometry + * A Geometry is a description of a geographic object. Create an instance of + * this class with the constructor. This is a base class, + * typical geometry types are described by subclasses of this class. + * + * Note that if you use the method, you must + * explicitly include the OpenLayers.Format.WKT in your build. + */ +OpenLayers.Geometry = OpenLayers.Class({ + + /** + * Property: id + * {String} A unique identifier for this geometry. + */ + id: null, + + /** + * Property: parent + * {}This is set when a Geometry is added as component + * of another geometry + */ + parent: null, + + /** + * Property: bounds + * {} The bounds of this geometry + */ + bounds: null, + + /** + * Constructor: OpenLayers.Geometry + * Creates a geometry object. + */ + initialize: function() { + this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME+ "_"); + }, + + /** + * Method: destroy + * Destroy this geometry. + */ + destroy: function() { + this.id = null; + this.bounds = null; + }, + + /** + * APIMethod: clone + * Create a clone of this geometry. Does not set any non-standard + * properties of the cloned geometry. + * + * Returns: + * {} An exact clone of this geometry. + */ + clone: function() { + return new OpenLayers.Geometry(); + }, + + /** + * Method: setBounds + * Set the bounds for this Geometry. + * + * Parameters: + * bounds - {} + */ + setBounds: function(bounds) { + if (bounds) { + this.bounds = bounds.clone(); + } + }, + + /** + * Method: clearBounds + * Nullify this components bounds and that of its parent as well. + */ + clearBounds: function() { + this.bounds = null; + if (this.parent) { + this.parent.clearBounds(); + } + }, + + /** + * Method: extendBounds + * Extend the existing bounds to include the new bounds. + * If geometry's bounds is not yet set, then set a new Bounds. + * + * Parameters: + * newBounds - {} + */ + extendBounds: function(newBounds){ + var bounds = this.getBounds(); + if (!bounds) { + this.setBounds(newBounds); + } else { + this.bounds.extend(newBounds); + } + }, + + /** + * APIMethod: getBounds + * Get the bounds for this Geometry. If bounds is not set, it + * is calculated again, this makes queries faster. + * + * Returns: + * {} + */ + getBounds: function() { + if (this.bounds == null) { + this.calculateBounds(); + } + return this.bounds; + }, + + /** + * APIMethod: calculateBounds + * Recalculate the bounds for the geometry. + */ + calculateBounds: function() { + // + // This should be overridden by subclasses. + // + }, + + /** + * APIMethod: distanceTo + * Calculate the closest distance between two geometries (on the x-y plane). + * + * Parameters: + * geometry - {} The target geometry. + * options - {Object} Optional properties for configuring the distance + * calculation. + * + * Valid options depend on the specific geometry type. + * + * Returns: + * {Number | Object} The distance between this geometry and the target. + * If details is true, the return will be an object with distance, + * x0, y0, x1, and x2 properties. The x0 and y0 properties represent + * the coordinates of the closest point on this geometry. The x1 and y1 + * properties represent the coordinates of the closest point on the + * target geometry. + */ + distanceTo: function(geometry, options) { + }, + + /** + * APIMethod: getVertices + * Return a list of all points in this geometry. + * + * Parameters: + * nodes - {Boolean} For lines, only return vertices that are + * endpoints. If false, for lines, only vertices that are not + * endpoints will be returned. If not provided, all vertices will + * be returned. + * + * Returns: + * {Array} A list of all vertices in the geometry. + */ + getVertices: function(nodes) { + }, + + /** + * Method: atPoint + * Note - This is only an approximation based on the bounds of the + * geometry. + * + * Parameters: + * lonlat - {|Object} OpenLayers.LonLat or an + * object with a 'lon' and 'lat' properties. + * toleranceLon - {float} Optional tolerance in Geometric Coords + * toleranceLat - {float} Optional tolerance in Geographic Coords + * + * Returns: + * {Boolean} Whether or not the geometry is at the specified location + */ + atPoint: function(lonlat, toleranceLon, toleranceLat) { + var atPoint = false; + var bounds = this.getBounds(); + if ((bounds != null) && (lonlat != null)) { + + var dX = (toleranceLon != null) ? toleranceLon : 0; + var dY = (toleranceLat != null) ? toleranceLat : 0; + + var toleranceBounds = + new OpenLayers.Bounds(this.bounds.left - dX, + this.bounds.bottom - dY, + this.bounds.right + dX, + this.bounds.top + dY); + + atPoint = toleranceBounds.containsLonLat(lonlat); + } + return atPoint; + }, + + /** + * Method: getLength + * Calculate the length of this geometry. This method is defined in + * subclasses. + * + * Returns: + * {Float} The length of the collection by summing its parts + */ + getLength: function() { + //to be overridden by geometries that actually have a length + // + return 0.0; + }, + + /** + * Method: getArea + * Calculate the area of this geometry. This method is defined in subclasses. + * + * Returns: + * {Float} The area of the collection by summing its parts + */ + getArea: function() { + //to be overridden by geometries that actually have an area + // + return 0.0; + }, + + /** + * APIMethod: getCentroid + * Calculate the centroid of this geometry. This method is defined in subclasses. + * + * Returns: + * {} The centroid of the collection + */ + getCentroid: function() { + return null; + }, + + /** + * Method: toString + * Returns a text representation of the geometry. If the WKT format is + * included in a build, this will be the Well-Known Text + * representation. + * + * Returns: + * {String} String representation of this geometry. + */ + toString: function() { + var string; + if (OpenLayers.Format && OpenLayers.Format.WKT) { + string = OpenLayers.Format.WKT.prototype.write( + new OpenLayers.Feature.Vector(this) + ); + } else { + string = Object.prototype.toString.call(this); + } + return string; + }, + + CLASS_NAME: "OpenLayers.Geometry" +}); + +/** + * Function: OpenLayers.Geometry.fromWKT + * Generate a geometry given a Well-Known Text string. For this method to + * work, you must include the OpenLayers.Format.WKT in your build + * explicitly. + * + * Parameters: + * wkt - {String} A string representing the geometry in Well-Known Text. + * + * Returns: + * {} A geometry of the appropriate class. + */ +OpenLayers.Geometry.fromWKT = function(wkt) { + var geom; + if (OpenLayers.Format && OpenLayers.Format.WKT) { + var format = OpenLayers.Geometry.fromWKT.format; + if (!format) { + format = new OpenLayers.Format.WKT(); + OpenLayers.Geometry.fromWKT.format = format; + } + var result = format.read(wkt); + if (result instanceof OpenLayers.Feature.Vector) { + geom = result.geometry; + } else if (OpenLayers.Util.isArray(result)) { + var len = result.length; + var components = new Array(len); + for (var i=0; i= seg2.x1 || seg2.x2 >= seg1.x1. In those + * obvious cases where there is no intersection, the function should + * not be called. + * + * Parameters: + * seg1 - {Object} Object representing a segment with properties x1, y1, x2, + * and y2. The start point is represented by x1 and y1. The end point + * is represented by x2 and y2. Start and end are ordered so that x1 < x2. + * seg2 - {Object} Object representing a segment with properties x1, y1, x2, + * and y2. The start point is represented by x1 and y1. The end point + * is represented by x2 and y2. Start and end are ordered so that x1 < x2. + * options - {Object} Optional properties for calculating the intersection. + * + * Valid options: + * point - {Boolean} Return the intersection point. If false, the actual + * intersection point will not be calculated. If true and the segments + * intersect, the intersection point will be returned. If true and + * the segments do not intersect, false will be returned. If true and + * the segments are coincident, true will be returned. + * tolerance - {Number} If a non-null value is provided, if the segments are + * within the tolerance distance, this will be considered an intersection. + * In addition, if the point option is true and the calculated intersection + * is within the tolerance distance of an end point, the endpoint will be + * returned instead of the calculated intersection. Further, if the + * intersection is within the tolerance of endpoints on both segments, or + * if two segment endpoints are within the tolerance distance of eachother + * (but no intersection is otherwise calculated), an endpoint on the + * first segment provided will be returned. + * + * Returns: + * {Boolean | } The two segments intersect. + * If the point argument is true, the return will be the intersection + * point or false if none exists. If point is true and the segments + * are coincident, return will be true (and the instersection is equal + * to the shorter segment). + */ +OpenLayers.Geometry.segmentsIntersect = function(seg1, seg2, options) { + var point = options && options.point; + var tolerance = options && options.tolerance; + var intersection = false; + var x11_21 = seg1.x1 - seg2.x1; + var y11_21 = seg1.y1 - seg2.y1; + var x12_11 = seg1.x2 - seg1.x1; + var y12_11 = seg1.y2 - seg1.y1; + var y22_21 = seg2.y2 - seg2.y1; + var x22_21 = seg2.x2 - seg2.x1; + var d = (y22_21 * x12_11) - (x22_21 * y12_11); + var n1 = (x22_21 * y11_21) - (y22_21 * x11_21); + var n2 = (x12_11 * y11_21) - (y12_11 * x11_21); + if(d == 0) { + // parallel + if(n1 == 0 && n2 == 0) { + // coincident + intersection = true; + } + } else { + var along1 = n1 / d; + var along2 = n2 / d; + if(along1 >= 0 && along1 <= 1 && along2 >=0 && along2 <= 1) { + // intersect + if(!point) { + intersection = true; + } else { + // calculate the intersection point + var x = seg1.x1 + (along1 * x12_11); + var y = seg1.y1 + (along1 * y12_11); + intersection = new OpenLayers.Geometry.Point(x, y); + } + } + } + if(tolerance) { + var dist; + if(intersection) { + if(point) { + var segs = [seg1, seg2]; + var seg, x, y; + // check segment endpoints for proximity to intersection + // set intersection to first endpoint within the tolerance + outer: for(var i=0; i<2; ++i) { + seg = segs[i]; + for(var j=1; j<3; ++j) { + x = seg["x" + j]; + y = seg["y" + j]; + dist = Math.sqrt( + Math.pow(x - intersection.x, 2) + + Math.pow(y - intersection.y, 2) + ); + if(dist < tolerance) { + intersection.x = x; + intersection.y = y; + break outer; + } + } + } + + } + } else { + // no calculated intersection, but segments could be within + // the tolerance of one another + var segs = [seg1, seg2]; + var source, target, x, y, p, result; + // check segment endpoints for proximity to intersection + // set intersection to first endpoint within the tolerance + outer: for(var i=0; i<2; ++i) { + source = segs[i]; + target = segs[(i+1)%2]; + for(var j=1; j<3; ++j) { + p = {x: source["x"+j], y: source["y"+j]}; + result = OpenLayers.Geometry.distanceToSegment(p, target); + if(result.distance < tolerance) { + if(point) { + intersection = new OpenLayers.Geometry.Point(p.x, p.y); + } else { + intersection = true; + } + break outer; + } + } + } + } + } + return intersection; +}; + +/** + * Function: OpenLayers.Geometry.distanceToSegment + * + * Parameters: + * point - {Object} An object with x and y properties representing the + * point coordinates. + * segment - {Object} An object with x1, y1, x2, and y2 properties + * representing endpoint coordinates. + * + * Returns: + * {Object} An object with distance, along, x, and y properties. The distance + * will be the shortest distance between the input point and segment. + * The x and y properties represent the coordinates along the segment + * where the shortest distance meets the segment. The along attribute + * describes how far between the two segment points the given point is. + */ +OpenLayers.Geometry.distanceToSegment = function(point, segment) { + var result = OpenLayers.Geometry.distanceSquaredToSegment(point, segment); + result.distance = Math.sqrt(result.distance); + return result; +}; + +/** + * Function: OpenLayers.Geometry.distanceSquaredToSegment + * + * Usually the distanceToSegment function should be used. This variant however + * can be used for comparisons where the exact distance is not important. + * + * Parameters: + * point - {Object} An object with x and y properties representing the + * point coordinates. + * segment - {Object} An object with x1, y1, x2, and y2 properties + * representing endpoint coordinates. + * + * Returns: + * {Object} An object with squared distance, along, x, and y properties. + * The distance will be the shortest distance between the input point and + * segment. The x and y properties represent the coordinates along the + * segment where the shortest distance meets the segment. The along + * attribute describes how far between the two segment points the given + * point is. + */ +OpenLayers.Geometry.distanceSquaredToSegment = function(point, segment) { + var x0 = point.x; + var y0 = point.y; + var x1 = segment.x1; + var y1 = segment.y1; + var x2 = segment.x2; + var y2 = segment.y2; + var dx = x2 - x1; + var dy = y2 - y1; + var along = ((dx * (x0 - x1)) + (dy * (y0 - y1))) / + (Math.pow(dx, 2) + Math.pow(dy, 2)); + var x, y; + if(along <= 0.0) { + x = x1; + y = y1; + } else if(along >= 1.0) { + x = x2; + y = y2; + } else { + x = x1 + along * dx; + y = y1 + along * dy; + } + return { + distance: Math.pow(x - x0, 2) + Math.pow(y - y0, 2), + x: x, y: y, + along: along + }; +}; +/* ====================================================================== + OpenLayers/Geometry/Collection.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Geometry.js + */ + +/** + * Class: OpenLayers.Geometry.Collection + * A Collection is exactly what it sounds like: A collection of different + * Geometries. These are stored in the local parameter (which + * can be passed as a parameter to the constructor). + * + * As new geometries are added to the collection, they are NOT cloned. + * When removing geometries, they need to be specified by reference (ie you + * have to pass in the *exact* geometry to be removed). + * + * The and functions here merely iterate through + * the components, summing their respective areas and lengths. + * + * Create a new instance with the constructor. + * + * Inherits from: + * - + */ +OpenLayers.Geometry.Collection = OpenLayers.Class(OpenLayers.Geometry, { + + /** + * APIProperty: components + * {Array()} The component parts of this geometry + */ + components: null, + + /** + * Property: componentTypes + * {Array(String)} An array of class names representing the types of + * components that the collection can include. A null value means the + * component types are not restricted. + */ + componentTypes: null, + + /** + * Constructor: OpenLayers.Geometry.Collection + * Creates a Geometry Collection -- a list of geoms. + * + * Parameters: + * components - {Array()} Optional array of geometries + * + */ + initialize: function (components) { + OpenLayers.Geometry.prototype.initialize.apply(this, arguments); + this.components = []; + if (components != null) { + this.addComponents(components); + } + }, + + /** + * APIMethod: destroy + * Destroy this geometry. + */ + destroy: function () { + this.components.length = 0; + this.components = null; + OpenLayers.Geometry.prototype.destroy.apply(this, arguments); + }, + + /** + * APIMethod: clone + * Clone this geometry. + * + * Returns: + * {} An exact clone of this collection + */ + clone: function() { + var geometry = eval("new " + this.CLASS_NAME + "()"); + for(var i=0, len=this.components.length; i)} An array of geometries to add + */ + addComponents: function(components){ + if(!(OpenLayers.Util.isArray(components))) { + components = [components]; + } + for(var i=0, len=components.length; i} A geometry to add + * index - {int} Optional index into the array to insert the component + * + * Returns: + * {Boolean} The component geometry was successfully added + */ + addComponent: function(component, index) { + var added = false; + if(component) { + if(this.componentTypes == null || + (OpenLayers.Util.indexOf(this.componentTypes, + component.CLASS_NAME) > -1)) { + + if(index != null && (index < this.components.length)) { + var components1 = this.components.slice(0, index); + var components2 = this.components.slice(index, + this.components.length); + components1.push(component); + this.components = components1.concat(components2); + } else { + this.components.push(component); + } + component.parent = this; + this.clearBounds(); + added = true; + } + } + return added; + }, + + /** + * APIMethod: removeComponents + * Remove components from this geometry. + * + * Parameters: + * components - {Array()} The components to be removed + * + * Returns: + * {Boolean} A component was removed. + */ + removeComponents: function(components) { + var removed = false; + + if(!(OpenLayers.Util.isArray(components))) { + components = [components]; + } + for(var i=components.length-1; i>=0; --i) { + removed = this.removeComponent(components[i]) || removed; + } + return removed; + }, + + /** + * Method: removeComponent + * Remove a component from this geometry. + * + * Parameters: + * component - {} + * + * Returns: + * {Boolean} The component was removed. + */ + removeComponent: function(component) { + + OpenLayers.Util.removeItem(this.components, component); + + // clearBounds() so that it gets recalculated on the next call + // to this.getBounds(); + this.clearBounds(); + return true; + }, + + /** + * APIMethod: getLength + * Calculate the length of this geometry + * + * Returns: + * {Float} The length of the geometry + */ + getLength: function() { + var length = 0.0; + for (var i=0, len=this.components.length; i. + * + * Returns: + * {Float} The area of the collection by summing its parts + */ + getArea: function() { + var area = 0.0; + for (var i=0, len=this.components.length; i} The spatial reference system + * for the geometry coordinates. If not provided, Geographic/WGS84 is + * assumed. + * + * Reference: + * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for + * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion + * Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409 + * + * Returns: + * {float} The approximate geodesic area of the geometry in square meters. + */ + getGeodesicArea: function(projection) { + var area = 0.0; + for(var i=0, len=this.components.length; i} The centroid of the collection + */ + getCentroid: function(weighted) { + if (!weighted) { + return this.components.length && this.components[0].getCentroid(); + } + var len = this.components.length; + if (!len) { + return false; + } + + var areas = []; + var centroids = []; + var areaSum = 0; + var minArea = Number.MAX_VALUE; + var component; + for (var i=0; i 0) ? area : minArea; + centroids.push(centroid); + } + len = areas.length; + if (areaSum === 0) { + // all the components in this collection have 0 area + // probably a collection of points -- weight all the points the same + for (var i=0; i} The spatial reference system + * for the geometry coordinates. If not provided, Geographic/WGS84 is + * assumed. + * + * Returns: + * {Float} The appoximate geodesic length of the geometry in meters. + */ + getGeodesicLength: function(projection) { + var length = 0.0; + for(var i=0, len=this.components.length; i} Center point for the rotation + */ + rotate: function(angle, origin) { + for(var i=0, len=this.components.length; i} Point of origin for resizing + * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1. + * + * Returns: + * {} - The current geometry. + */ + resize: function(scale, origin, ratio) { + for(var i=0; i} The target geometry. + * options - {Object} Optional properties for configuring the distance + * calculation. + * + * Valid options: + * details - {Boolean} Return details from the distance calculation. + * Default is false. + * edge - {Boolean} Calculate the distance from this geometry to the + * nearest edge of the target geometry. Default is true. If true, + * calling distanceTo from a geometry that is wholly contained within + * the target will result in a non-zero distance. If false, whenever + * geometries intersect, calling distanceTo will return 0. If false, + * details cannot be returned. + * + * Returns: + * {Number | Object} The distance between this geometry and the target. + * If details is true, the return will be an object with distance, + * x0, y0, x1, and y1 properties. The x0 and y0 properties represent + * the coordinates of the closest point on this geometry. The x1 and y1 + * properties represent the coordinates of the closest point on the + * target geometry. + */ + distanceTo: function(geometry, options) { + var edge = !(options && options.edge === false); + var details = edge && options && options.details; + var result, best, distance; + var min = Number.POSITIVE_INFINITY; + for(var i=0, len=this.components.length; i} The geometry to test. + * + * Returns: + * {Boolean} The supplied geometry is equivalent to this geometry. + */ + equals: function(geometry) { + var equivalent = true; + if(!geometry || !geometry.CLASS_NAME || + (this.CLASS_NAME != geometry.CLASS_NAME)) { + equivalent = false; + } else if(!(OpenLayers.Util.isArray(geometry.components)) || + (geometry.components.length != this.components.length)) { + equivalent = false; + } else { + for(var i=0, len=this.components.length; i} + * dest - {} + * + * Returns: + * {} + */ + transform: function(source, dest) { + if (source && dest) { + for (var i=0, len=this.components.length; i} Any type of geometry. + * + * Returns: + * {Boolean} The input geometry intersects this one. + */ + intersects: function(geometry) { + var intersect = false; + for(var i=0, len=this.components.length; i + */ +OpenLayers.Geometry.Point = OpenLayers.Class(OpenLayers.Geometry, { + + /** + * APIProperty: x + * {float} + */ + x: null, + + /** + * APIProperty: y + * {float} + */ + y: null, + + /** + * Constructor: OpenLayers.Geometry.Point + * Construct a point geometry. + * + * Parameters: + * x - {float} + * y - {float} + * + */ + initialize: function(x, y) { + OpenLayers.Geometry.prototype.initialize.apply(this, arguments); + + this.x = parseFloat(x); + this.y = parseFloat(y); + }, + + /** + * APIMethod: clone + * + * Returns: + * {} An exact clone of this OpenLayers.Geometry.Point + */ + clone: function(obj) { + if (obj == null) { + obj = new OpenLayers.Geometry.Point(this.x, this.y); + } + + // catch any randomly tagged-on properties + OpenLayers.Util.applyDefaults(obj, this); + + return obj; + }, + + /** + * Method: calculateBounds + * Create a new Bounds based on the lon/lat + */ + calculateBounds: function () { + this.bounds = new OpenLayers.Bounds(this.x, this.y, + this.x, this.y); + }, + + /** + * APIMethod: distanceTo + * Calculate the closest distance between two geometries (on the x-y plane). + * + * Parameters: + * geometry - {} The target geometry. + * options - {Object} Optional properties for configuring the distance + * calculation. + * + * Valid options: + * details - {Boolean} Return details from the distance calculation. + * Default is false. + * edge - {Boolean} Calculate the distance from this geometry to the + * nearest edge of the target geometry. Default is true. If true, + * calling distanceTo from a geometry that is wholly contained within + * the target will result in a non-zero distance. If false, whenever + * geometries intersect, calling distanceTo will return 0. If false, + * details cannot be returned. + * + * Returns: + * {Number | Object} The distance between this geometry and the target. + * If details is true, the return will be an object with distance, + * x0, y0, x1, and x2 properties. The x0 and y0 properties represent + * the coordinates of the closest point on this geometry. The x1 and y1 + * properties represent the coordinates of the closest point on the + * target geometry. + */ + distanceTo: function(geometry, options) { + var edge = !(options && options.edge === false); + var details = edge && options && options.details; + var distance, x0, y0, x1, y1, result; + if(geometry instanceof OpenLayers.Geometry.Point) { + x0 = this.x; + y0 = this.y; + x1 = geometry.x; + y1 = geometry.y; + distance = Math.sqrt(Math.pow(x0 - x1, 2) + Math.pow(y0 - y1, 2)); + result = !details ? + distance : {x0: x0, y0: y0, x1: x1, y1: y1, distance: distance}; + } else { + result = geometry.distanceTo(this, options); + if(details) { + // switch coord order since this geom is target + result = { + x0: result.x1, y0: result.y1, + x1: result.x0, y1: result.y0, + distance: result.distance + }; + } + } + return result; + }, + + /** + * APIMethod: equals + * Determine whether another geometry is equivalent to this one. Geometries + * are considered equivalent if all components have the same coordinates. + * + * Parameters: + * geom - {} The geometry to test. + * + * Returns: + * {Boolean} The supplied geometry is equivalent to this geometry. + */ + equals: function(geom) { + var equals = false; + if (geom != null) { + equals = ((this.x == geom.x && this.y == geom.y) || + (isNaN(this.x) && isNaN(this.y) && isNaN(geom.x) && isNaN(geom.y))); + } + return equals; + }, + + /** + * Method: toShortString + * + * Returns: + * {String} Shortened String representation of Point object. + * (ex. "5, 42") + */ + toShortString: function() { + return (this.x + ", " + this.y); + }, + + /** + * APIMethod: move + * Moves a geometry by the given displacement along positive x and y axes. + * This modifies the position of the geometry and clears the cached + * bounds. + * + * Parameters: + * x - {Float} Distance to move geometry in positive x direction. + * y - {Float} Distance to move geometry in positive y direction. + */ + move: function(x, y) { + this.x = this.x + x; + this.y = this.y + y; + this.clearBounds(); + }, + + /** + * APIMethod: rotate + * Rotate a point around another. + * + * Parameters: + * angle - {Float} Rotation angle in degrees (measured counterclockwise + * from the positive x-axis) + * origin - {} Center point for the rotation + */ + rotate: function(angle, origin) { + angle *= Math.PI / 180; + var radius = this.distanceTo(origin); + var theta = angle + Math.atan2(this.y - origin.y, this.x - origin.x); + this.x = origin.x + (radius * Math.cos(theta)); + this.y = origin.y + (radius * Math.sin(theta)); + this.clearBounds(); + }, + + /** + * APIMethod: getCentroid + * + * Returns: + * {} The centroid of the collection + */ + getCentroid: function() { + return new OpenLayers.Geometry.Point(this.x, this.y); + }, + + /** + * APIMethod: resize + * Resize a point relative to some origin. For points, this has the effect + * of scaling a vector (from the origin to the point). This method is + * more useful on geometry collection subclasses. + * + * Parameters: + * scale - {Float} Ratio of the new distance from the origin to the old + * distance from the origin. A scale of 2 doubles the + * distance between the point and origin. + * origin - {} Point of origin for resizing + * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1. + * + * Returns: + * {} - The current geometry. + */ + resize: function(scale, origin, ratio) { + ratio = (ratio == undefined) ? 1 : ratio; + this.x = origin.x + (scale * ratio * (this.x - origin.x)); + this.y = origin.y + (scale * (this.y - origin.y)); + this.clearBounds(); + return this; + }, + + /** + * APIMethod: intersects + * Determine if the input geometry intersects this one. + * + * Parameters: + * geometry - {} Any type of geometry. + * + * Returns: + * {Boolean} The input geometry intersects this one. + */ + intersects: function(geometry) { + var intersect = false; + if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") { + intersect = this.equals(geometry); + } else { + intersect = geometry.intersects(this); + } + return intersect; + }, + + /** + * APIMethod: transform + * Translate the x,y properties of the point from source to dest. + * + * Parameters: + * source - {} + * dest - {} + * + * Returns: + * {} + */ + transform: function(source, dest) { + if ((source && dest)) { + OpenLayers.Projection.transform( + this, source, dest); + this.bounds = null; + } + return this; + }, + + /** + * APIMethod: getVertices + * Return a list of all points in this geometry. + * + * Parameters: + * nodes - {Boolean} For lines, only return vertices that are + * endpoints. If false, for lines, only vertices that are not + * endpoints will be returned. If not provided, all vertices will + * be returned. + * + * Returns: + * {Array} A list of all vertices in the geometry. + */ + getVertices: function(nodes) { + return [this]; + }, + + CLASS_NAME: "OpenLayers.Geometry.Point" +}); +/* ====================================================================== + OpenLayers/Geometry/MultiPoint.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Geometry/Collection.js + * @requires OpenLayers/Geometry/Point.js + */ + +/** + * Class: OpenLayers.Geometry.MultiPoint + * MultiPoint is a collection of Points. Create a new instance with the + * constructor. + * + * Inherits from: + * - + * - + */ +OpenLayers.Geometry.MultiPoint = OpenLayers.Class( + OpenLayers.Geometry.Collection, { + + /** + * Property: componentTypes + * {Array(String)} An array of class names representing the types of + * components that the collection can include. A null value means the + * component types are not restricted. + */ + componentTypes: ["OpenLayers.Geometry.Point"], + + /** + * Constructor: OpenLayers.Geometry.MultiPoint + * Create a new MultiPoint Geometry + * + * Parameters: + * components - {Array()} + * + * Returns: + * {} + */ + + /** + * APIMethod: addPoint + * Wrapper for + * + * Parameters: + * point - {} Point to be added + * index - {Integer} Optional index + */ + addPoint: function(point, index) { + this.addComponent(point, index); + }, + + /** + * APIMethod: removePoint + * Wrapper for + * + * Parameters: + * point - {} Point to be removed + */ + removePoint: function(point){ + this.removeComponent(point); + }, + + CLASS_NAME: "OpenLayers.Geometry.MultiPoint" +}); +/* ====================================================================== + OpenLayers/Geometry/Curve.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Geometry/MultiPoint.js + */ + +/** + * Class: OpenLayers.Geometry.Curve + * A Curve is a MultiPoint, whose points are assumed to be connected. To + * this end, we provide a "getLength()" function, which iterates through + * the points, summing the distances between them. + * + * Inherits: + * - + */ +OpenLayers.Geometry.Curve = OpenLayers.Class(OpenLayers.Geometry.MultiPoint, { + + /** + * Property: componentTypes + * {Array(String)} An array of class names representing the types of + * components that the collection can include. A null + * value means the component types are not restricted. + */ + componentTypes: ["OpenLayers.Geometry.Point"], + + /** + * Constructor: OpenLayers.Geometry.Curve + * + * Parameters: + * point - {Array()} + */ + + /** + * APIMethod: getLength + * + * Returns: + * {Float} The length of the curve + */ + getLength: function() { + var length = 0.0; + if ( this.components && (this.components.length > 1)) { + for(var i=1, len=this.components.length; i} The spatial reference system + * for the geometry coordinates. If not provided, Geographic/WGS84 is + * assumed. + * + * Returns: + * {Float} The appoximate geodesic length of the geometry in meters. + */ + getGeodesicLength: function(projection) { + var geom = this; // so we can work with a clone if needed + if(projection) { + var gg = new OpenLayers.Projection("EPSG:4326"); + if(!gg.equals(projection)) { + geom = this.clone().transform(projection, gg); + } + } + var length = 0.0; + if(geom.components && (geom.components.length > 1)) { + var p1, p2; + for(var i=1, len=geom.components.length; i + */ +OpenLayers.Geometry.LineString = OpenLayers.Class(OpenLayers.Geometry.Curve, { + + /** + * Constructor: OpenLayers.Geometry.LineString + * Create a new LineString geometry + * + * Parameters: + * points - {Array()} An array of points used to + * generate the linestring + * + */ + + /** + * APIMethod: removeComponent + * Only allows removal of a point if there are three or more points in + * the linestring. (otherwise the result would be just a single point) + * + * Parameters: + * point - {} The point to be removed + * + * Returns: + * {Boolean} The component was removed. + */ + removeComponent: function(point) { + var removed = this.components && (this.components.length > 2); + if (removed) { + OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this, + arguments); + } + return removed; + }, + + /** + * APIMethod: intersects + * Test for instersection between two geometries. This is a cheapo + * implementation of the Bently-Ottmann algorigithm. It doesn't + * really keep track of a sweep line data structure. It is closer + * to the brute force method, except that segments are sorted and + * potential intersections are only calculated when bounding boxes + * intersect. + * + * Parameters: + * geometry - {} + * + * Returns: + * {Boolean} The input geometry intersects this geometry. + */ + intersects: function(geometry) { + var intersect = false; + var type = geometry.CLASS_NAME; + if(type == "OpenLayers.Geometry.LineString" || + type == "OpenLayers.Geometry.LinearRing" || + type == "OpenLayers.Geometry.Point") { + var segs1 = this.getSortedSegments(); + var segs2; + if(type == "OpenLayers.Geometry.Point") { + segs2 = [{ + x1: geometry.x, y1: geometry.y, + x2: geometry.x, y2: geometry.y + }]; + } else { + segs2 = geometry.getSortedSegments(); + } + var seg1, seg1x1, seg1x2, seg1y1, seg1y2, + seg2, seg2y1, seg2y2; + // sweep right + outer: for(var i=0, len=segs1.length; i seg1x2) { + // seg1 still left of seg2 + break; + } + if(seg2.x2 < seg1x1) { + // seg2 still left of seg1 + continue; + } + seg2y1 = seg2.y1; + seg2y2 = seg2.y2; + if(Math.min(seg2y1, seg2y2) > Math.max(seg1y1, seg1y2)) { + // seg2 above seg1 + continue; + } + if(Math.max(seg2y1, seg2y2) < Math.min(seg1y1, seg1y2)) { + // seg2 below seg1 + continue; + } + if(OpenLayers.Geometry.segmentsIntersect(seg1, seg2)) { + intersect = true; + break outer; + } + } + } + } else { + intersect = geometry.intersects(this); + } + return intersect; + }, + + /** + * Method: getSortedSegments + * + * Returns: + * {Array} An array of segment objects. Segment objects have properties + * x1, y1, x2, and y2. The start point is represented by x1 and y1. + * The end point is represented by x2 and y2. Start and end are + * ordered so that x1 < x2. + */ + getSortedSegments: function() { + var numSeg = this.components.length - 1; + var segments = new Array(numSeg), point1, point2; + for(var i=0; i 0) { + // sort intersections along segment + var xDir = seg.x1 < seg.x2 ? 1 : -1; + var yDir = seg.y1 < seg.y2 ? 1 : -1; + result = { + lines: lines, + points: intersections.sort(function(p1, p2) { + return (xDir * p1.x - xDir * p2.x) || (yDir * p1.y - yDir * p2.y); + }) + }; + } + return result; + }, + + /** + * Method: split + * Use this geometry (the source) to attempt to split a target geometry. + * + * Parameters: + * target - {} The target geometry. + * options - {Object} Properties of this object will be used to determine + * how the split is conducted. + * + * Valid options: + * mutual - {Boolean} Split the source geometry in addition to the target + * geometry. Default is false. + * edge - {Boolean} Allow splitting when only edges intersect. Default is + * true. If false, a vertex on the source must be within the tolerance + * distance of the intersection to be considered a split. + * tolerance - {Number} If a non-null value is provided, intersections + * within the tolerance distance of an existing vertex on the source + * will be assumed to occur at the vertex. + * + * Returns: + * {Array} A list of geometries (of this same type as the target) that + * result from splitting the target with the source geometry. The + * source and target geometry will remain unmodified. If no split + * results, null will be returned. If mutual is true and a split + * results, return will be an array of two arrays - the first will be + * all geometries that result from splitting the source geometry and + * the second will be all geometries that result from splitting the + * target geometry. + */ + split: function(target, options) { + var results = null; + var mutual = options && options.mutual; + var sourceSplit, targetSplit, sourceParts, targetParts; + if(target instanceof OpenLayers.Geometry.LineString) { + var verts = this.getVertices(); + var vert1, vert2, seg, splits, lines, point; + var points = []; + sourceParts = []; + for(var i=0, stop=verts.length-2; i<=stop; ++i) { + vert1 = verts[i]; + vert2 = verts[i+1]; + seg = { + x1: vert1.x, y1: vert1.y, + x2: vert2.x, y2: vert2.y + }; + targetParts = targetParts || [target]; + if(mutual) { + points.push(vert1.clone()); + } + for(var j=0; j 0) { + lines.unshift(j, 1); + Array.prototype.splice.apply(targetParts, lines); + j += lines.length - 2; + } + if(mutual) { + for(var k=0, len=splits.points.length; k 0 && points.length > 0) { + points.push(vert2.clone()); + sourceParts.push(new OpenLayers.Geometry.LineString(points)); + } + } else { + results = target.splitWith(this, options); + } + if(targetParts && targetParts.length > 1) { + targetSplit = true; + } else { + targetParts = []; + } + if(sourceParts && sourceParts.length > 1) { + sourceSplit = true; + } else { + sourceParts = []; + } + if(targetSplit || sourceSplit) { + if(mutual) { + results = [sourceParts, targetParts]; + } else { + results = targetParts; + } + } + return results; + }, + + /** + * Method: splitWith + * Split this geometry (the target) with the given geometry (the source). + * + * Parameters: + * geometry - {} A geometry used to split this + * geometry (the source). + * options - {Object} Properties of this object will be used to determine + * how the split is conducted. + * + * Valid options: + * mutual - {Boolean} Split the source geometry in addition to the target + * geometry. Default is false. + * edge - {Boolean} Allow splitting when only edges intersect. Default is + * true. If false, a vertex on the source must be within the tolerance + * distance of the intersection to be considered a split. + * tolerance - {Number} If a non-null value is provided, intersections + * within the tolerance distance of an existing vertex on the source + * will be assumed to occur at the vertex. + * + * Returns: + * {Array} A list of geometries (of this same type as the target) that + * result from splitting the target with the source geometry. The + * source and target geometry will remain unmodified. If no split + * results, null will be returned. If mutual is true and a split + * results, return will be an array of two arrays - the first will be + * all geometries that result from splitting the source geometry and + * the second will be all geometries that result from splitting the + * target geometry. + */ + splitWith: function(geometry, options) { + return geometry.split(this, options); + + }, + + /** + * APIMethod: getVertices + * Return a list of all points in this geometry. + * + * Parameters: + * nodes - {Boolean} For lines, only return vertices that are + * endpoints. If false, for lines, only vertices that are not + * endpoints will be returned. If not provided, all vertices will + * be returned. + * + * Returns: + * {Array} A list of all vertices in the geometry. + */ + getVertices: function(nodes) { + var vertices; + if(nodes === true) { + vertices = [ + this.components[0], + this.components[this.components.length-1] + ]; + } else if (nodes === false) { + vertices = this.components.slice(1, this.components.length-1); + } else { + vertices = this.components.slice(); + } + return vertices; + }, + + /** + * APIMethod: distanceTo + * Calculate the closest distance between two geometries (on the x-y plane). + * + * Parameters: + * geometry - {} The target geometry. + * options - {Object} Optional properties for configuring the distance + * calculation. + * + * Valid options: + * details - {Boolean} Return details from the distance calculation. + * Default is false. + * edge - {Boolean} Calculate the distance from this geometry to the + * nearest edge of the target geometry. Default is true. If true, + * calling distanceTo from a geometry that is wholly contained within + * the target will result in a non-zero distance. If false, whenever + * geometries intersect, calling distanceTo will return 0. If false, + * details cannot be returned. + * + * Returns: + * {Number | Object} The distance between this geometry and the target. + * If details is true, the return will be an object with distance, + * x0, y0, x1, and x2 properties. The x0 and y0 properties represent + * the coordinates of the closest point on this geometry. The x1 and y1 + * properties represent the coordinates of the closest point on the + * target geometry. + */ + distanceTo: function(geometry, options) { + var edge = !(options && options.edge === false); + var details = edge && options && options.details; + var result, best = {}; + var min = Number.POSITIVE_INFINITY; + if(geometry instanceof OpenLayers.Geometry.Point) { + var segs = this.getSortedSegments(); + var x = geometry.x; + var y = geometry.y; + var seg; + for(var i=0, len=segs.length; i x && ((y > seg.y1 && y < seg.y2) || (y < seg.y1 && y > seg.y2))) { + break; + } + } + } + if(details) { + best = { + distance: best.distance, + x0: best.x, y0: best.y, + x1: x, y1: y + }; + } else { + best = best.distance; + } + } else if(geometry instanceof OpenLayers.Geometry.LineString) { + var segs0 = this.getSortedSegments(); + var segs1 = geometry.getSortedSegments(); + var seg0, seg1, intersection, x0, y0; + var len1 = segs1.length; + var interOptions = {point: true}; + outer: for(var i=0, len=segs0.length; i maxDistance) { + maxDistance = distance; + indexFarthest = index; + } + } + + if (maxDistance > tolerance && indexFarthest != firstPoint) { + //Add the largest point that exceeds the tolerance + pointIndexsToKeep.push(indexFarthest); + douglasPeuckerReduction(points, firstPoint, indexFarthest, tolerance); + douglasPeuckerReduction(points, indexFarthest, lastPoint, tolerance); + } + }; + + /** + * Private function calculating the perpendicular distance + * TODO: check whether OpenLayers.Geometry.LineString::distanceTo() is faster or slower + */ + var perpendicularDistance = function(point1, point2, point){ + //Area = |(1/2)(x1y2 + x2y3 + x3y1 - x2y1 - x3y2 - x1y3)| *Area of triangle + //Base = v((x1-x2)²+(x1-x2)²) *Base of Triangle* + //Area = .5*Base*H *Solve for height + //Height = Area/.5/Base + + 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)); + var bottom = Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2)); + var height = area / bottom * 2; + + return height; + }; + + var firstPoint = 0; + var lastPoint = points.length - 1; + var pointIndexsToKeep = []; + + //Add the first and last index to the keepers + pointIndexsToKeep.push(firstPoint); + pointIndexsToKeep.push(lastPoint); + + //The first and the last point cannot be the same + while (points[firstPoint].equals(points[lastPoint])) { + lastPoint--; + //Addition: the first point not equal to first point in the LineString is kept as well + pointIndexsToKeep.push(lastPoint); + } + + douglasPeuckerReduction(points, firstPoint, lastPoint, tolerance); + var returnPoints = []; + pointIndexsToKeep.sort(compareNumbers); + for (var index = 0; index < pointIndexsToKeep.length; index++) { + returnPoints.push(points[pointIndexsToKeep[index]]); + } + return new OpenLayers.Geometry.LineString(returnPoints); + + } + else { + return this; + } + }, + + CLASS_NAME: "OpenLayers.Geometry.LineString" +}); +/* ====================================================================== + OpenLayers/Geometry/LinearRing.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Geometry/LineString.js + */ + +/** + * Class: OpenLayers.Geometry.LinearRing + * + * A Linear Ring is a special LineString which is closed. It closes itself + * automatically on every addPoint/removePoint by adding a copy of the first + * point as the last point. + * + * Also, as it is the first in the line family to close itself, a getArea() + * function is defined to calculate the enclosed area of the linearRing + * + * Inherits: + * - + */ +OpenLayers.Geometry.LinearRing = OpenLayers.Class( + OpenLayers.Geometry.LineString, { + + /** + * Property: componentTypes + * {Array(String)} An array of class names representing the types of + * components that the collection can include. A null + * value means the component types are not restricted. + */ + componentTypes: ["OpenLayers.Geometry.Point"], + + /** + * Constructor: OpenLayers.Geometry.LinearRing + * Linear rings are constructed with an array of points. This array + * can represent a closed or open ring. If the ring is open (the last + * point does not equal the first point), the constructor will close + * the ring. If the ring is already closed (the last point does equal + * the first point), it will be left closed. + * + * Parameters: + * points - {Array()} points + */ + + /** + * APIMethod: addComponent + * Adds a point to geometry components. If the point is to be added to + * the end of the components array and it is the same as the last point + * already in that array, the duplicate point is not added. This has + * the effect of closing the ring if it is not already closed, and + * doing the right thing if it is already closed. This behavior can + * be overridden by calling the method with a non-null index as the + * second argument. + * + * Parameters: + * point - {} + * index - {Integer} Index into the array to insert the component + * + * Returns: + * {Boolean} Was the Point successfully added? + */ + addComponent: function(point, index) { + var added = false; + + //remove last point + var lastPoint = this.components.pop(); + + // given an index, add the point + // without an index only add non-duplicate points + if(index != null || !point.equals(lastPoint)) { + added = OpenLayers.Geometry.Collection.prototype.addComponent.apply(this, + arguments); + } + + //append copy of first point + var firstPoint = this.components[0]; + OpenLayers.Geometry.Collection.prototype.addComponent.apply(this, + [firstPoint]); + + return added; + }, + + /** + * APIMethod: removeComponent + * Removes a point from geometry components. + * + * Parameters: + * point - {} + * + * Returns: + * {Boolean} The component was removed. + */ + removeComponent: function(point) { + var removed = this.components && (this.components.length > 3); + if (removed) { + //remove last point + this.components.pop(); + + //remove our point + OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this, + arguments); + //append copy of first point + var firstPoint = this.components[0]; + OpenLayers.Geometry.Collection.prototype.addComponent.apply(this, + [firstPoint]); + } + return removed; + }, + + /** + * APIMethod: move + * Moves a geometry by the given displacement along positive x and y axes. + * This modifies the position of the geometry and clears the cached + * bounds. + * + * Parameters: + * x - {Float} Distance to move geometry in positive x direction. + * y - {Float} Distance to move geometry in positive y direction. + */ + move: function(x, y) { + for(var i = 0, len=this.components.length; i} Center point for the rotation + */ + rotate: function(angle, origin) { + for(var i=0, len=this.components.length; i} Point of origin for resizing + * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1. + * + * Returns: + * {} - The current geometry. + */ + resize: function(scale, origin, ratio) { + for(var i=0, len=this.components.length; i} + * dest - {} + * + * Returns: + * {} + */ + transform: function(source, dest) { + if (source && dest) { + for (var i=0, len=this.components.length; i} The centroid of the collection + */ + getCentroid: function() { + if (this.components) { + var len = this.components.length; + if (len > 0 && len <= 2) { + return this.components[0].clone(); + } else if (len > 2) { + var sumX = 0.0; + var sumY = 0.0; + var x0 = this.components[0].x; + var y0 = this.components[0].y; + var area = -1 * this.getArea(); + if (area != 0) { + for (var i = 0; i < len - 1; i++) { + var b = this.components[i]; + var c = this.components[i+1]; + sumX += (b.x + c.x - 2 * x0) * ((b.x - x0) * (c.y - y0) - (c.x - x0) * (b.y - y0)); + sumY += (b.y + c.y - 2 * y0) * ((b.x - x0) * (c.y - y0) - (c.x - x0) * (b.y - y0)); + } + var x = x0 + sumX / (6 * area); + var y = y0 + sumY / (6 * area); + } else { + for (var i = 0; i < len - 1; i++) { + sumX += this.components[i].x; + sumY += this.components[i].y; + } + var x = sumX / (len - 1); + var y = sumY / (len - 1); + } + return new OpenLayers.Geometry.Point(x, y); + } else { + return null; + } + } + }, + + /** + * APIMethod: getArea + * Note - The area is positive if the ring is oriented CW, otherwise + * it will be negative. + * + * Returns: + * {Float} The signed area for a ring. + */ + getArea: function() { + var area = 0.0; + if ( this.components && (this.components.length > 2)) { + var sum = 0.0; + for (var i=0, len=this.components.length; i} The spatial reference system + * for the geometry coordinates. If not provided, Geographic/WGS84 is + * assumed. + * + * Reference: + * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for + * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion + * Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409 + * + * Returns: + * {float} The approximate signed geodesic area of the polygon in square + * meters. + */ + getGeodesicArea: function(projection) { + var ring = this; // so we can work with a clone if needed + if(projection) { + var gg = new OpenLayers.Projection("EPSG:4326"); + if(!gg.equals(projection)) { + ring = this.clone().transform(projection, gg); + } + } + var area = 0.0; + var len = ring.components && ring.components.length; + if(len > 2) { + var p1, p2; + for(var i=0; i} + * + * Returns: + * {Boolean | Number} The point is inside the linear ring. Returns 1 if + * the point is coincident with an edge. Returns boolean otherwise. + */ + containsPoint: function(point) { + var approx = OpenLayers.Number.limitSigDigs; + var digs = 14; + var px = approx(point.x, digs); + var py = approx(point.y, digs); + function getX(y, x1, y1, x2, y2) { + return (y - y2) * ((x2 - x1) / (y2 - y1)) + x2; + } + var numSeg = this.components.length - 1; + var start, end, x1, y1, x2, y2, cx, cy; + var crosses = 0; + for(var i=0; i= x1 && px <= x2) || // right or vert + x1 >= x2 && (px <= x1 && px >= x2)) { // left or vert + // point on edge + crosses = -1; + break; + } + } + // ignore other horizontal edges + continue; + } + cx = approx(getX(py, x1, y1, x2, y2), digs); + if(cx == px) { + // point on line + if(y1 < y2 && (py >= y1 && py <= y2) || // upward + y1 > y2 && (py <= y1 && py >= y2)) { // downward + // point on edge + crosses = -1; + break; + } + } + if(cx <= px) { + // no crossing to the right + continue; + } + if(x1 != x2 && (cx < Math.min(x1, x2) || cx > Math.max(x1, x2))) { + // no crossing + continue; + } + if(y1 < y2 && (py >= y1 && py < y2) || // upward + y1 > y2 && (py < y1 && py >= y2)) { // downward + ++crosses; + } + } + var contained = (crosses == -1) ? + // on edge + 1 : + // even (out) or odd (in) + !!(crosses & 1); + + return contained; + }, + + /** + * APIMethod: intersects + * Determine if the input geometry intersects this one. + * + * Parameters: + * geometry - {} Any type of geometry. + * + * Returns: + * {Boolean} The input geometry intersects this one. + */ + intersects: function(geometry) { + var intersect = false; + if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") { + intersect = this.containsPoint(geometry); + } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LineString") { + intersect = geometry.intersects(this); + } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") { + intersect = OpenLayers.Geometry.LineString.prototype.intersects.apply( + this, [geometry] + ); + } else { + // check for component intersections + for(var i=0, len=geometry.components.length; i transform-origin + * or WebkitTransformOrigin -> -webkit-transform-origin + * + * Parameters: + * prefixedDom - {String} The property to convert + * + * Returns: + * {String} The CSS property + */ + function domToCss(prefixedDom) { + if (!prefixedDom) { return null; } + return prefixedDom. + replace(/([A-Z])/g, function(c) { return "-" + c.toLowerCase(); }). + replace(/^ms-/, "-ms-"); + } + + /** + * APIMethod: css + * Detect which property is used for a CSS property + * + * Parameters: + * property - {String} The standard (unprefixed) CSS property name + * + * Returns: + * {String} The standard CSS property, prefixed property or null if not + * supported + */ + function css(property) { + if (cssCache[property] === undefined) { + var domProperty = property. + replace(/(-[\s\S])/g, function(c) { return c.charAt(1).toUpperCase(); }); + var prefixedDom = style(domProperty); + cssCache[property] = domToCss(prefixedDom); + } + return cssCache[property]; + } + + /** + * APIMethod: js + * Detect which property is used for a JS property/method + * + * Parameters: + * obj - {Object} The object to test on + * property - {String} The standard (unprefixed) JS property name + * + * Returns: + * {String} The standard JS property, prefixed property or null if not + * supported + */ + function js(obj, property) { + if (jsCache[property] === undefined) { + var tmpProp, + i = 0, + l = VENDOR_PREFIXES.length, + prefix, + isStyleObj = (typeof obj.cssText !== "undefined"); + + jsCache[property] = null; + for(; i in series for some + * duration. + * + * Parameters: + * callback - {Function} The function to be called at the next animation frame. + * duration - {Number} Optional duration for the loop. If not provided, the + * animation loop will execute indefinitely. + * element - {DOMElement} Optional element that visually bounds the animation. + * + * Returns: + * {Number} Identifier for the animation loop. Used to stop animations with + * . + */ + function start(callback, duration, element) { + duration = duration > 0 ? duration : Number.POSITIVE_INFINITY; + var id = ++counter; + var start = +new Date; + loops[id] = function() { + if (loops[id] && +new Date - start <= duration) { + callback(); + if (loops[id]) { + requestFrame(loops[id], element); + } + } else { + delete loops[id]; + } + }; + requestFrame(loops[id], element); + return id; + } + + /** + * Function: stop + * Terminates an animation loop started with . + * + * Parameters: + * id - {Number} Identifier returned from . + */ + function stop(id) { + delete loops[id]; + } + + return { + isNative: isNative, + requestFrame: requestFrame, + start: start, + stop: stop + }; + +})(window); +/* ====================================================================== + OpenLayers/Tween.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/BaseTypes/Class.js + * @requires OpenLayers/Animation.js + */ + +/** + * Namespace: OpenLayers.Tween + */ +OpenLayers.Tween = OpenLayers.Class({ + + /** + * APIProperty: easing + * {(Function)} Easing equation used for the animation + * Defaultly set to OpenLayers.Easing.Expo.easeOut + */ + easing: null, + + /** + * APIProperty: begin + * {Object} Values to start the animation with + */ + begin: null, + + /** + * APIProperty: finish + * {Object} Values to finish the animation with + */ + finish: null, + + /** + * APIProperty: duration + * {int} duration of the tween (number of steps) + */ + duration: null, + + /** + * APIProperty: callbacks + * {Object} An object with start, eachStep and done properties whose values + * are functions to be call during the animation. They are passed the + * current computed value as argument. + */ + callbacks: null, + + /** + * Property: time + * {int} Step counter + */ + time: null, + + /** + * APIProperty: minFrameRate + * {Number} The minimum framerate for animations in frames per second. After + * each step, the time spent in the animation is compared to the calculated + * time at this frame rate. If the animation runs longer than the calculated + * time, the next step is skipped. Default is 30. + */ + minFrameRate: null, + + /** + * Property: startTime + * {Number} The timestamp of the first execution step. Used for skipping + * frames + */ + startTime: null, + + /** + * Property: animationId + * {int} Loop id returned by OpenLayers.Animation.start + */ + animationId: null, + + /** + * Property: playing + * {Boolean} Tells if the easing is currently playing + */ + playing: false, + + /** + * Constructor: OpenLayers.Tween + * Creates a Tween. + * + * Parameters: + * easing - {(Function)} easing function method to use + */ + initialize: function(easing) { + this.easing = (easing) ? easing : OpenLayers.Easing.Expo.easeOut; + }, + + /** + * APIMethod: start + * Plays the Tween, and calls the callback method on each step + * + * Parameters: + * begin - {Object} values to start the animation with + * finish - {Object} values to finish the animation with + * duration - {int} duration of the tween (number of steps) + * options - {Object} hash of options (callbacks (start, eachStep, done), + * minFrameRate) + */ + start: function(begin, finish, duration, options) { + this.playing = true; + this.begin = begin; + this.finish = finish; + this.duration = duration; + this.callbacks = options.callbacks; + this.minFrameRate = options.minFrameRate || 30; + this.time = 0; + this.startTime = new Date().getTime(); + OpenLayers.Animation.stop(this.animationId); + this.animationId = null; + if (this.callbacks && this.callbacks.start) { + this.callbacks.start.call(this, this.begin); + } + this.animationId = OpenLayers.Animation.start( + OpenLayers.Function.bind(this.play, this) + ); + }, + + /** + * APIMethod: stop + * Stops the Tween, and calls the done callback + * Doesn't do anything if animation is already finished + */ + stop: function() { + if (!this.playing) { + return; + } + + if (this.callbacks && this.callbacks.done) { + this.callbacks.done.call(this, this.finish); + } + OpenLayers.Animation.stop(this.animationId); + this.animationId = null; + this.playing = false; + }, + + /** + * Method: play + * Calls the appropriate easing method + */ + play: function() { + var value = {}; + for (var i in this.begin) { + var b = this.begin[i]; + var f = this.finish[i]; + if (b == null || f == null || isNaN(b) || isNaN(f)) { + throw new TypeError('invalid value for Tween'); + } + + var c = f - b; + value[i] = this.easing.apply(this, [this.time, b, c, this.duration]); + } + this.time++; + + if (this.callbacks && this.callbacks.eachStep) { + // skip frames if frame rate drops below threshold + if ((new Date().getTime() - this.startTime) / this.time <= 1000 / this.minFrameRate) { + this.callbacks.eachStep.call(this, value); + } + } + + if (this.time > this.duration) { + this.stop(); + } + }, + + /** + * Create empty functions for all easing methods. + */ + CLASS_NAME: "OpenLayers.Tween" +}); + +/** + * Namespace: OpenLayers.Easing + * + * Credits: + * Easing Equations by Robert Penner, + */ +OpenLayers.Easing = { + /** + * Create empty functions for all easing methods. + */ + CLASS_NAME: "OpenLayers.Easing" +}; + +/** + * Namespace: OpenLayers.Easing.Linear + */ +OpenLayers.Easing.Linear = { + + /** + * Function: easeIn + * + * Parameters: + * t - {Float} time + * b - {Float} beginning position + * c - {Float} total change + * d - {Float} duration of the transition + * + * Returns: + * {Float} + */ + easeIn: function(t, b, c, d) { + return c*t/d + b; + }, + + /** + * Function: easeOut + * + * Parameters: + * t - {Float} time + * b - {Float} beginning position + * c - {Float} total change + * d - {Float} duration of the transition + * + * Returns: + * {Float} + */ + easeOut: function(t, b, c, d) { + return c*t/d + b; + }, + + /** + * Function: easeInOut + * + * Parameters: + * t - {Float} time + * b - {Float} beginning position + * c - {Float} total change + * d - {Float} duration of the transition + * + * Returns: + * {Float} + */ + easeInOut: function(t, b, c, d) { + return c*t/d + b; + }, + + CLASS_NAME: "OpenLayers.Easing.Linear" +}; + +/** + * Namespace: OpenLayers.Easing.Expo + */ +OpenLayers.Easing.Expo = { + + /** + * Function: easeIn + * + * Parameters: + * t - {Float} time + * b - {Float} beginning position + * c - {Float} total change + * d - {Float} duration of the transition + * + * Returns: + * {Float} + */ + easeIn: function(t, b, c, d) { + return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b; + }, + + /** + * Function: easeOut + * + * Parameters: + * t - {Float} time + * b - {Float} beginning position + * c - {Float} total change + * d - {Float} duration of the transition + * + * Returns: + * {Float} + */ + easeOut: function(t, b, c, d) { + return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b; + }, + + /** + * Function: easeInOut + * + * Parameters: + * t - {Float} time + * b - {Float} beginning position + * c - {Float} total change + * d - {Float} duration of the transition + * + * Returns: + * {Float} + */ + easeInOut: function(t, b, c, d) { + if (t==0) return b; + if (t==d) return b+c; + if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b; + return c/2 * (-Math.pow(2, -10 * --t) + 2) + b; + }, + + CLASS_NAME: "OpenLayers.Easing.Expo" +}; + +/** + * Namespace: OpenLayers.Easing.Quad + */ +OpenLayers.Easing.Quad = { + + /** + * Function: easeIn + * + * Parameters: + * t - {Float} time + * b - {Float} beginning position + * c - {Float} total change + * d - {Float} duration of the transition + * + * Returns: + * {Float} + */ + easeIn: function(t, b, c, d) { + return c*(t/=d)*t + b; + }, + + /** + * Function: easeOut + * + * Parameters: + * t - {Float} time + * b - {Float} beginning position + * c - {Float} total change + * d - {Float} duration of the transition + * + * Returns: + * {Float} + */ + easeOut: function(t, b, c, d) { + return -c *(t/=d)*(t-2) + b; + }, + + /** + * Function: easeInOut + * + * Parameters: + * t - {Float} time + * b - {Float} beginning position + * c - {Float} total change + * d - {Float} duration of the transition + * + * Returns: + * {Float} + */ + easeInOut: function(t, b, c, d) { + if ((t/=d/2) < 1) return c/2*t*t + b; + return -c/2 * ((--t)*(t-2) - 1) + b; + }, + + CLASS_NAME: "OpenLayers.Easing.Quad" +}; +/* ====================================================================== + OpenLayers/Projection.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/BaseTypes/Class.js + * @requires OpenLayers/Util.js + */ + +/** + * Namespace: OpenLayers.Projection + * Methods for coordinate transforms between coordinate systems. By default, + * OpenLayers ships with the ability to transform coordinates between + * geographic (EPSG:4326) and web or spherical mercator (EPSG:900913 et al.) + * coordinate reference systems. See the method for details + * on usage. + * + * Additional transforms may be added by using the + * library. If the proj4js library is included, the method + * will work between any two coordinate reference systems with proj4js + * definitions. + * + * If the proj4js library is not included, or if you wish to allow transforms + * between arbitrary coordinate reference systems, use the + * method to register a custom transform method. + */ +OpenLayers.Projection = OpenLayers.Class({ + + /** + * Property: proj + * {Object} Proj4js.Proj instance. + */ + proj: null, + + /** + * Property: projCode + * {String} + */ + projCode: null, + + /** + * Property: titleRegEx + * {RegExp} regular expression to strip the title from a proj4js definition + */ + titleRegEx: /\+title=[^\+]*/, + + /** + * Constructor: OpenLayers.Projection + * This class offers several methods for interacting with a wrapped + * pro4js projection object. + * + * Parameters: + * projCode - {String} A string identifying the Well Known Identifier for + * the projection. + * options - {Object} An optional object to set additional properties + * on the projection. + * + * Returns: + * {} A projection object. + */ + initialize: function(projCode, options) { + OpenLayers.Util.extend(this, options); + this.projCode = projCode; + if (typeof Proj4js == "object") { + this.proj = new Proj4js.Proj(projCode); + } + }, + + /** + * APIMethod: getCode + * Get the string SRS code. + * + * Returns: + * {String} The SRS code. + */ + getCode: function() { + return this.proj ? this.proj.srsCode : this.projCode; + }, + + /** + * APIMethod: getUnits + * Get the units string for the projection -- returns null if + * proj4js is not available. + * + * Returns: + * {String} The units abbreviation. + */ + getUnits: function() { + return this.proj ? this.proj.units : null; + }, + + /** + * Method: toString + * Convert projection to string (getCode wrapper). + * + * Returns: + * {String} The projection code. + */ + toString: function() { + return this.getCode(); + }, + + /** + * Method: equals + * Test equality of two projection instances. Determines equality based + * soley on the projection code. + * + * Returns: + * {Boolean} The two projections are equivalent. + */ + equals: function(projection) { + var p = projection, equals = false; + if (p) { + if (!(p instanceof OpenLayers.Projection)) { + p = new OpenLayers.Projection(p); + } + if ((typeof Proj4js == "object") && this.proj.defData && p.proj.defData) { + equals = this.proj.defData.replace(this.titleRegEx, "") == + p.proj.defData.replace(this.titleRegEx, ""); + } else if (p.getCode) { + var source = this.getCode(), target = p.getCode(); + equals = source == target || + !!OpenLayers.Projection.transforms[source] && + OpenLayers.Projection.transforms[source][target] === + OpenLayers.Projection.nullTransform; + } + } + return equals; + }, + + /* Method: destroy + * Destroy projection object. + */ + destroy: function() { + delete this.proj; + delete this.projCode; + }, + + CLASS_NAME: "OpenLayers.Projection" +}); + +/** + * Property: transforms + * {Object} Transforms is an object, with from properties, each of which may + * have a to property. This allows you to define projections without + * requiring support for proj4js to be included. + * + * This object has keys which correspond to a 'source' projection object. The + * keys should be strings, corresponding to the projection.getCode() value. + * Each source projection object should have a set of destination projection + * keys included in the object. + * + * Each value in the destination object should be a transformation function, + * where the function is expected to be passed an object with a .x and a .y + * property. The function should return the object, with the .x and .y + * transformed according to the transformation function. + * + * Note - Properties on this object should not be set directly. To add a + * transform method to this object, use the method. For an + * example of usage, see the OpenLayers.Layer.SphericalMercator file. + */ +OpenLayers.Projection.transforms = {}; + +/** + * APIProperty: defaults + * {Object} Defaults for the SRS codes known to OpenLayers (currently + * EPSG:4326, CRS:84, urn:ogc:def:crs:EPSG:6.6:4326, EPSG:900913, EPSG:3857, + * EPSG:102113 and EPSG:102100). Keys are the SRS code, values are units, + * maxExtent (the validity extent for the SRS) and yx (true if this SRS is + * known to have a reverse axis order). + */ +OpenLayers.Projection.defaults = { + "EPSG:4326": { + units: "degrees", + maxExtent: [-180, -90, 180, 90], + yx: true + }, + "CRS:84": { + units: "degrees", + maxExtent: [-180, -90, 180, 90] + }, + "EPSG:900913": { + units: "m", + maxExtent: [-20037508.34, -20037508.34, 20037508.34, 20037508.34] + } +}; + +/** + * APIMethod: addTransform + * Set a custom transform method between two projections. Use this method in + * cases where the proj4js lib is not available or where custom projections + * need to be handled. + * + * Parameters: + * from - {String} The code for the source projection + * to - {String} the code for the destination projection + * method - {Function} A function that takes a point as an argument and + * transforms that point from the source to the destination projection + * in place. The original point should be modified. + */ +OpenLayers.Projection.addTransform = function(from, to, method) { + if (method === OpenLayers.Projection.nullTransform) { + var defaults = OpenLayers.Projection.defaults[from]; + if (defaults && !OpenLayers.Projection.defaults[to]) { + OpenLayers.Projection.defaults[to] = defaults; + } + } + if(!OpenLayers.Projection.transforms[from]) { + OpenLayers.Projection.transforms[from] = {}; + } + OpenLayers.Projection.transforms[from][to] = method; +}; + +/** + * APIMethod: transform + * Transform a point coordinate from one projection to another. Note that + * the input point is transformed in place. + * + * Parameters: + * point - { | Object} An object with x and y + * properties representing coordinates in those dimensions. + * source - {OpenLayers.Projection} Source map coordinate system + * dest - {OpenLayers.Projection} Destination map coordinate system + * + * Returns: + * point - {object} A transformed coordinate. The original point is modified. + */ +OpenLayers.Projection.transform = function(point, source, dest) { + if (source && dest) { + if (!(source instanceof OpenLayers.Projection)) { + source = new OpenLayers.Projection(source); + } + if (!(dest instanceof OpenLayers.Projection)) { + dest = new OpenLayers.Projection(dest); + } + if (source.proj && dest.proj) { + point = Proj4js.transform(source.proj, dest.proj, point); + } else { + var sourceCode = source.getCode(); + var destCode = dest.getCode(); + var transforms = OpenLayers.Projection.transforms; + if (transforms[sourceCode] && transforms[sourceCode][destCode]) { + transforms[sourceCode][destCode](point); + } + } + } + return point; +}; + +/** + * APIFunction: nullTransform + * A null transformation - useful for defining projection aliases when + * proj4js is not available: + * + * (code) + * OpenLayers.Projection.addTransform("EPSG:3857", "EPSG:900913", + * OpenLayers.Projection.nullTransform); + * OpenLayers.Projection.addTransform("EPSG:900913", "EPSG:3857", + * OpenLayers.Projection.nullTransform); + * (end) + */ +OpenLayers.Projection.nullTransform = function(point) { + return point; +}; + +/** + * Note: Transforms for web mercator <-> geographic + * OpenLayers recognizes EPSG:3857, EPSG:900913, EPSG:102113 and EPSG:102100. + * OpenLayers originally started referring to EPSG:900913 as web mercator. + * The EPSG has declared EPSG:3857 to be web mercator. + * ArcGIS 10 recognizes the EPSG:3857, EPSG:102113, and EPSG:102100 as + * equivalent. See http://blogs.esri.com/Dev/blogs/arcgisserver/archive/2009/11/20/ArcGIS-Online-moving-to-Google-_2F00_-Bing-tiling-scheme_3A00_-What-does-this-mean-for-you_3F00_.aspx#12084. + * For geographic, OpenLayers recognizes EPSG:4326, CRS:84 and + * urn:ogc:def:crs:EPSG:6.6:4326. OpenLayers also knows about the reverse axis + * order for EPSG:4326. + */ +(function() { + + var pole = 20037508.34; + + function inverseMercator(xy) { + xy.x = 180 * xy.x / pole; + xy.y = 180 / Math.PI * (2 * Math.atan(Math.exp((xy.y / pole) * Math.PI)) - Math.PI / 2); + return xy; + } + + function forwardMercator(xy) { + xy.x = xy.x * pole / 180; + var y = Math.log(Math.tan((90 + xy.y) * Math.PI / 360)) / Math.PI * pole; + xy.y = Math.max(-20037508.34, Math.min(y, 20037508.34)); + return xy; + } + + function map(base, codes) { + var add = OpenLayers.Projection.addTransform; + var same = OpenLayers.Projection.nullTransform; + var i, len, code, other, j; + for (i=0, len=codes.length; i=0; --i) { + map(mercator[i], geographic); + } + for (i=geographic.length-1; i>=0; --i) { + map(geographic[i], mercator); + } + +})(); +/* ====================================================================== + OpenLayers/Map.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/BaseTypes/Class.js + * @requires OpenLayers/Util.js + * @requires OpenLayers/Util/vendorPrefix.js + * @requires OpenLayers/Events.js + * @requires OpenLayers/Tween.js + * @requires OpenLayers/Projection.js + */ + +/** + * Class: OpenLayers.Map + * Instances of OpenLayers.Map are interactive maps embedded in a web page. + * Create a new map with the constructor. + * + * On their own maps do not provide much functionality. To extend a map + * it's necessary to add controls () and + * layers () to the map. + */ +OpenLayers.Map = OpenLayers.Class({ + + /** + * Constant: Z_INDEX_BASE + * {Object} Base z-indexes for different classes of thing + */ + Z_INDEX_BASE: { + BaseLayer: 100, + Overlay: 325, + Feature: 725, + Popup: 750, + Control: 1000 + }, + + /** + * APIProperty: events + * {} + * + * Register a listener for a particular event with the following syntax: + * (code) + * map.events.register(type, obj, listener); + * (end) + * + * Listeners will be called with a reference to an event object. The + * properties of this event depends on exactly what happened. + * + * All event objects have at least the following properties: + * object - {Object} A reference to map.events.object. + * element - {DOMElement} A reference to map.events.element. + * + * Browser events have the following additional properties: + * xy - {} The pixel location of the event (relative + * to the the map viewport). + * + * Supported map event types: + * preaddlayer - triggered before a layer has been added. The event + * object will include a *layer* property that references the layer + * to be added. When a listener returns "false" the adding will be + * aborted. + * addlayer - triggered after a layer has been added. The event object + * will include a *layer* property that references the added layer. + * preremovelayer - triggered before a layer has been removed. The event + * object will include a *layer* property that references the layer + * to be removed. When a listener returns "false" the removal will be + * aborted. + * removelayer - triggered after a layer has been removed. The event + * object will include a *layer* property that references the removed + * layer. + * changelayer - triggered after a layer name change, order change, + * opacity change, params change, visibility change (actual visibility, + * not the layer's visibility property) or attribution change (due to + * extent change). Listeners will receive an event object with *layer* + * and *property* properties. The *layer* property will be a reference + * to the changed layer. The *property* property will be a key to the + * changed property (name, order, opacity, params, visibility or + * attribution). + * movestart - triggered after the start of a drag, pan, or zoom. The event + * object may include a *zoomChanged* property that tells whether the + * zoom has changed. + * move - triggered after each drag, pan, or zoom + * moveend - triggered after a drag, pan, or zoom completes + * zoomend - triggered after a zoom completes + * mouseover - triggered after mouseover the map + * mouseout - triggered after mouseout the map + * mousemove - triggered after mousemove the map + * changebaselayer - triggered after the base layer changes + * updatesize - triggered after the method was executed + */ + + /** + * Property: id + * {String} Unique identifier for the map + */ + id: null, + + /** + * Property: fractionalZoom + * {Boolean} For a base layer that supports it, allow the map resolution + * to be set to a value between one of the values in the resolutions + * array. Default is false. + * + * When fractionalZoom is set to true, it is possible to zoom to + * an arbitrary extent. This requires a base layer from a source + * that supports requests for arbitrary extents (i.e. not cached + * tiles on a regular lattice). This means that fractionalZoom + * will not work with commercial layers (Google, Yahoo, VE), layers + * using TileCache, or any other pre-cached data sources. + * + * If you are using fractionalZoom, then you should also use + * instead of layer.resolutions[zoom] as the + * former works for non-integer zoom levels. + */ + fractionalZoom: false, + + /** + * APIProperty: events + * {} An events object that handles all + * events on the map + */ + events: null, + + /** + * APIProperty: allOverlays + * {Boolean} Allow the map to function with "overlays" only. Defaults to + * false. If true, the lowest layer in the draw order will act as + * the base layer. In addition, if set to true, all layers will + * have isBaseLayer set to false when they are added to the map. + * + * Note: + * If you set map.allOverlays to true, then you *cannot* use + * map.setBaseLayer or layer.setIsBaseLayer. With allOverlays true, + * the lowest layer in the draw layer is the base layer. So, to change + * the base layer, use or to set the layer + * index to 0. + */ + allOverlays: false, + + /** + * APIProperty: div + * {DOMElement|String} The element that contains the map (or an id for + * that element). If the constructor is called + * with two arguments, this should be provided as the first argument. + * Alternatively, the map constructor can be called with the options + * object as the only argument. In this case (one argument), a + * div property may or may not be provided. If the div property + * is not provided, the map can be rendered to a container later + * using the method. + * + * Note: + * If you are calling after map construction, do not use + * auto. Instead, divide your by your + * maximum expected dimension. + */ + div: null, + + /** + * Property: dragging + * {Boolean} The map is currently being dragged. + */ + dragging: false, + + /** + * Property: size + * {} Size of the main div (this.div) + */ + size: null, + + /** + * Property: viewPortDiv + * {HTMLDivElement} The element that represents the map viewport + */ + viewPortDiv: null, + + /** + * Property: layerContainerOrigin + * {} The lonlat at which the later container was + * re-initialized (on-zoom) + */ + layerContainerOrigin: null, + + /** + * Property: layerContainerDiv + * {HTMLDivElement} The element that contains the layers. + */ + layerContainerDiv: null, + + /** + * APIProperty: layers + * {Array()} Ordered list of layers in the map + */ + layers: null, + + /** + * APIProperty: controls + * {Array()} List of controls associated with the map. + * + * If not provided in the map options at construction, the map will + * by default be given the following controls if present in the build: + * - or + * - or + * - + * - + */ + controls: null, + + /** + * Property: popups + * {Array()} List of popups associated with the map + */ + popups: null, + + /** + * APIProperty: baseLayer + * {} The currently selected base layer. This determines + * min/max zoom level, projection, etc. + */ + baseLayer: null, + + /** + * Property: center + * {} The current center of the map + */ + center: null, + + /** + * Property: resolution + * {Float} The resolution of the map. + */ + resolution: null, + + /** + * Property: zoom + * {Integer} The current zoom level of the map + */ + zoom: 0, + + /** + * Property: panRatio + * {Float} The ratio of the current extent within + * which panning will tween. + */ + panRatio: 1.5, + + /** + * APIProperty: options + * {Object} The options object passed to the class constructor. Read-only. + */ + options: null, + + // Options + + /** + * APIProperty: tileSize + * {} Set in the map options to override the default tile + * size for this map. + */ + tileSize: null, + + /** + * APIProperty: projection + * {String} Set in the map options to specify the default projection + * for layers added to this map. When using a projection other than EPSG:4326 + * (CRS:84, Geographic) or EPSG:3857 (EPSG:900913, Web Mercator), + * also set maxExtent, maxResolution or resolutions. Default is "EPSG:4326". + * Note that the projection of the map is usually determined + * by that of the current baseLayer (see and ). + */ + projection: "EPSG:4326", + + /** + * APIProperty: units + * {String} The map units. Possible values are 'degrees' (or 'dd'), 'm', + * 'ft', 'km', 'mi', 'inches'. Normally taken from the projection. + * Only required if both map and layers do not define a projection, + * or if they define a projection which does not define units + */ + units: null, + + /** + * APIProperty: resolutions + * {Array(Float)} A list of map resolutions (map units per pixel) in + * descending order. If this is not set in the layer constructor, it + * will be set based on other resolution related properties + * (maxExtent, maxResolution, maxScale, etc.). + */ + resolutions: null, + + /** + * APIProperty: maxResolution + * {Float} Required if you are not displaying the whole world on a tile + * with the size specified in . + */ + maxResolution: null, + + /** + * APIProperty: minResolution + * {Float} + */ + minResolution: null, + + /** + * APIProperty: maxScale + * {Float} + */ + maxScale: null, + + /** + * APIProperty: minScale + * {Float} + */ + minScale: null, + + /** + * APIProperty: maxExtent + * {|Array} If provided as an array, the array + * should consist of four values (left, bottom, right, top). + * The maximum extent for the map. + * Default depends on projection; if this is one of those defined in OpenLayers.Projection.defaults + * (EPSG:4326 or web mercator), maxExtent will be set to the value defined there; + * else, defaults to null. + * To restrict user panning and zooming of the map, use instead. + * The value for will change calculations for tile URLs. + */ + maxExtent: null, + + /** + * APIProperty: minExtent + * {|Array} If provided as an array, the array + * should consist of four values (left, bottom, right, top). + * The minimum extent for the map. Defaults to null. + */ + minExtent: null, + + /** + * APIProperty: restrictedExtent + * {|Array} If provided as an array, the array + * should consist of four values (left, bottom, right, top). + * Limit map navigation to this extent where possible. + * If a non-null restrictedExtent is set, panning will be restricted + * to the given bounds. In addition, zooming to a resolution that + * displays more than the restricted extent will center the map + * on the restricted extent. If you wish to limit the zoom level + * or resolution, use maxResolution. + */ + restrictedExtent: null, + + /** + * APIProperty: numZoomLevels + * {Integer} Number of zoom levels for the map. Defaults to 16. Set a + * different value in the map options if needed. + */ + numZoomLevels: 16, + + /** + * APIProperty: theme + * {String} Relative path to a CSS file from which to load theme styles. + * Specify null in the map options (e.g. {theme: null}) if you + * want to get cascading style declarations - by putting links to + * stylesheets or style declarations directly in your page. + */ + theme: null, + + /** + * APIProperty: displayProjection + * {} Requires proj4js support for projections other + * than EPSG:4326 or EPSG:900913/EPSG:3857. Projection used by + * several controls to display data to user. If this property is set, + * it will be set on any control which has a null displayProjection + * property at the time the control is added to the map. + */ + displayProjection: null, + + /** + * APIProperty: tileManager + * {|Object} By default, and if the build contains + * TileManager.js, the map will use the TileManager to queue image requests + * and to cache tile image elements. To create a map without a TileManager + * configure the map with tileManager: null. To create a TileManager with + * non-default options, supply the options instead or alternatively supply + * an instance of {}. + */ + + /** + * APIProperty: fallThrough + * {Boolean} Should OpenLayers allow events on the map to fall through to + * other elements on the page, or should it swallow them? (#457) + * Default is to swallow. + */ + fallThrough: false, + + /** + * APIProperty: autoUpdateSize + * {Boolean} Should OpenLayers automatically update the size of the map + * when the resize event is fired. Default is true. + */ + autoUpdateSize: true, + + /** + * APIProperty: eventListeners + * {Object} If set as an option at construction, the eventListeners + * object will be registered with . Object + * structure must be a listeners object as shown in the example for + * the events.on method. + */ + eventListeners: null, + + /** + * Property: panTween + * {} Animated panning tween object, see panTo() + */ + panTween: null, + + /** + * APIProperty: panMethod + * {Function} The Easing function to be used for tweening. Default is + * OpenLayers.Easing.Expo.easeOut. Setting this to 'null' turns off + * animated panning. + */ + panMethod: OpenLayers.Easing.Expo.easeOut, + + /** + * Property: panDuration + * {Integer} The number of steps to be passed to the + * OpenLayers.Tween.start() method when the map is + * panned. + * Default is 50. + */ + panDuration: 50, + + /** + * Property: zoomTween + * {} Animated zooming tween object, see zoomTo() + */ + zoomTween: null, + + /** + * APIProperty: zoomMethod + * {Function} The Easing function to be used for tweening. Default is + * OpenLayers.Easing.Quad.easeOut. Setting this to 'null' turns off + * animated zooming. + */ + zoomMethod: OpenLayers.Easing.Quad.easeOut, + + /** + * Property: zoomDuration + * {Integer} The number of steps to be passed to the + * OpenLayers.Tween.start() method when the map is zoomed. + * Default is 20. + */ + zoomDuration: 20, + + /** + * Property: paddingForPopups + * {} Outside margin of the popup. Used to prevent + * the popup from getting too close to the map border. + */ + paddingForPopups : null, + + /** + * Property: layerContainerOriginPx + * {Object} Cached object representing the layer container origin (in pixels). + */ + layerContainerOriginPx: null, + + /** + * Property: minPx + * {Object} An object with a 'x' and 'y' values that is the lower + * left of maxExtent in viewport pixel space. + * Used to verify in moveByPx that the new location we're moving to + * is valid. It is also used in the getLonLatFromViewPortPx function + * of Layer. + */ + minPx: null, + + /** + * Property: maxPx + * {Object} An object with a 'x' and 'y' values that is the top + * right of maxExtent in viewport pixel space. + * Used to verify in moveByPx that the new location we're moving to + * is valid. + */ + maxPx: null, + + /** + * Constructor: OpenLayers.Map + * Constructor for a new OpenLayers.Map instance. There are two possible + * ways to call the map constructor. See the examples below. + * + * Parameters: + * div - {DOMElement|String} The element or id of an element in your page + * that will contain the map. May be omitted if the
option is + * provided or if you intend to call the method later. + * options - {Object} Optional object with properties to tag onto the map. + * + * Valid options (in addition to the listed API properties): + * center - {|Array} The default initial center of the map. + * If provided as array, the first value is the x coordinate, + * and the 2nd value is the y coordinate. + * Only specify if is provided. + * Note that if an ArgParser/Permalink control is present, + * and the querystring contains coordinates, center will be set + * by that, and this option will be ignored. + * zoom - {Number} The initial zoom level for the map. Only specify if + * is provided. + * Note that if an ArgParser/Permalink control is present, + * and the querystring contains a zoom level, zoom will be set + * by that, and this option will be ignored. + * extent - {|Array} The initial extent of the map. + * If provided as an array, the array should consist of + * four values (left, bottom, right, top). + * Only specify if
and are not provided. + * + * Examples: + * (code) + * // create a map with default options in an element with the id "map1" + * var map = new OpenLayers.Map("map1"); + * + * // create a map with non-default options in an element with id "map2" + * var options = { + * projection: "EPSG:3857", + * maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000), + * center: new OpenLayers.LonLat(-12356463.476333, 5621521.4854095) + * }; + * var map = new OpenLayers.Map("map2", options); + * + * // map with non-default options - same as above but with a single argument, + * // a restricted extent, and using arrays for bounds and center + * var map = new OpenLayers.Map({ + * div: "map_id", + * projection: "EPSG:3857", + * maxExtent: [-18924313.432222, -15538711.094146, 18924313.432222, 15538711.094146], + * restrictedExtent: [-13358338.893333, -9608371.5085962, 13358338.893333, 9608371.5085962], + * center: [-12356463.476333, 5621521.4854095] + * }); + * + * // create a map without a reference to a container - call render later + * var map = new OpenLayers.Map({ + * projection: "EPSG:3857", + * maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000) + * }); + * (end) + */ + initialize: function (div, options) { + + // If only one argument is provided, check if it is an object. + if(arguments.length === 1 && typeof div === "object") { + options = div; + div = options && options.div; + } + + // Simple-type defaults are set in class definition. + // Now set complex-type defaults + this.tileSize = new OpenLayers.Size(OpenLayers.Map.TILE_WIDTH, + OpenLayers.Map.TILE_HEIGHT); + + this.paddingForPopups = new OpenLayers.Bounds(15, 15, 15, 15); + + this.theme = OpenLayers._getScriptLocation() + + 'theme/default/style.css'; + + // backup original options + this.options = OpenLayers.Util.extend({}, options); + + // now override default options + OpenLayers.Util.extend(this, options); + + var projCode = this.projection instanceof OpenLayers.Projection ? + this.projection.projCode : this.projection; + OpenLayers.Util.applyDefaults(this, OpenLayers.Projection.defaults[projCode]); + + // allow extents and center to be arrays + if (this.maxExtent && !(this.maxExtent instanceof OpenLayers.Bounds)) { + this.maxExtent = new OpenLayers.Bounds(this.maxExtent); + } + if (this.minExtent && !(this.minExtent instanceof OpenLayers.Bounds)) { + this.minExtent = new OpenLayers.Bounds(this.minExtent); + } + if (this.restrictedExtent && !(this.restrictedExtent instanceof OpenLayers.Bounds)) { + this.restrictedExtent = new OpenLayers.Bounds(this.restrictedExtent); + } + if (this.center && !(this.center instanceof OpenLayers.LonLat)) { + this.center = new OpenLayers.LonLat(this.center); + } + + // initialize layers array + this.layers = []; + + this.id = OpenLayers.Util.createUniqueID("OpenLayers.Map_"); + + this.div = OpenLayers.Util.getElement(div); + if(!this.div) { + this.div = document.createElement("div"); + this.div.style.height = "1px"; + this.div.style.width = "1px"; + } + + OpenLayers.Element.addClass(this.div, 'olMap'); + + // the viewPortDiv is the outermost div we modify + var id = this.id + "_OpenLayers_ViewPort"; + this.viewPortDiv = OpenLayers.Util.createDiv(id, null, null, null, + "relative", null, + "hidden"); + this.viewPortDiv.style.width = "100%"; + this.viewPortDiv.style.height = "100%"; + this.viewPortDiv.className = "olMapViewport"; + this.div.appendChild(this.viewPortDiv); + + this.events = new OpenLayers.Events( + this, this.viewPortDiv, null, this.fallThrough, + {includeXY: true} + ); + + if (OpenLayers.TileManager && this.tileManager !== null) { + if (!(this.tileManager instanceof OpenLayers.TileManager)) { + this.tileManager = new OpenLayers.TileManager(this.tileManager); + } + this.tileManager.addMap(this); + } + + // the layerContainerDiv is the one that holds all the layers + id = this.id + "_OpenLayers_Container"; + this.layerContainerDiv = OpenLayers.Util.createDiv(id); + this.layerContainerDiv.style.zIndex=this.Z_INDEX_BASE['Popup']-1; + this.layerContainerOriginPx = {x: 0, y: 0}; + this.applyTransform(); + + this.viewPortDiv.appendChild(this.layerContainerDiv); + + this.updateSize(); + if(this.eventListeners instanceof Object) { + this.events.on(this.eventListeners); + } + + if (this.autoUpdateSize === true) { + // updateSize on catching the window's resize + // Note that this is ok, as updateSize() does nothing if the + // map's size has not actually changed. + this.updateSizeDestroy = OpenLayers.Function.bind(this.updateSize, + this); + OpenLayers.Event.observe(window, 'resize', + this.updateSizeDestroy); + } + + // only append link stylesheet if the theme property is set + if(this.theme) { + // check existing links for equivalent url + var addNode = true; + var nodes = document.getElementsByTagName('link'); + for(var i=0, len=nodes.length; i=0; --i) { + this.controls[i].destroy(); + } + this.controls = null; + } + if (this.layers != null) { + for (var i = this.layers.length - 1; i>=0; --i) { + //pass 'false' to destroy so that map wont try to set a new + // baselayer after each baselayer is removed + this.layers[i].destroy(false); + } + this.layers = null; + } + if (this.viewPortDiv && this.viewPortDiv.parentNode) { + this.viewPortDiv.parentNode.removeChild(this.viewPortDiv); + } + this.viewPortDiv = null; + + if (this.tileManager) { + this.tileManager.removeMap(this); + this.tileManager = null; + } + + if(this.eventListeners) { + this.events.un(this.eventListeners); + this.eventListeners = null; + } + this.events.destroy(); + this.events = null; + + this.options = null; + }, + + /** + * APIMethod: setOptions + * Change the map options + * + * Parameters: + * options - {Object} Hashtable of options to tag to the map + */ + setOptions: function(options) { + var updatePxExtent = this.minPx && + options.restrictedExtent != this.restrictedExtent; + OpenLayers.Util.extend(this, options); + // force recalculation of minPx and maxPx + updatePxExtent && this.moveTo(this.getCachedCenter(), this.zoom, { + forceZoomChange: true + }); + }, + + /** + * APIMethod: getTileSize + * Get the tile size for the map + * + * Returns: + * {} + */ + getTileSize: function() { + return this.tileSize; + }, + + + /** + * APIMethod: getBy + * Get a list of objects given a property and a match item. + * + * Parameters: + * array - {String} A property on the map whose value is an array. + * property - {String} A property on each item of the given array. + * match - {String | Object} A string to match. Can also be a regular + * expression literal or object. In addition, it can be any object + * with a method named test. For reqular expressions or other, if + * match.test(map[array][i][property]) evaluates to true, the item will + * be included in the array returned. If no items are found, an empty + * array is returned. + * + * Returns: + * {Array} An array of items where the given property matches the given + * criteria. + */ + getBy: function(array, property, match) { + var test = (typeof match.test == "function"); + var found = OpenLayers.Array.filter(this[array], function(item) { + return item[property] == match || (test && match.test(item[property])); + }); + return found; + }, + + /** + * APIMethod: getLayersBy + * Get a list of layers with properties matching the given criteria. + * + * Parameters: + * property - {String} A layer property to be matched. + * match - {String | Object} A string to match. Can also be a regular + * expression literal or object. In addition, it can be any object + * with a method named test. For reqular expressions or other, if + * match.test(layer[property]) evaluates to true, the layer will be + * included in the array returned. If no layers are found, an empty + * array is returned. + * + * Returns: + * {Array()} A list of layers matching the given criteria. + * An empty array is returned if no matches are found. + */ + getLayersBy: function(property, match) { + return this.getBy("layers", property, match); + }, + + /** + * APIMethod: getLayersByName + * Get a list of layers with names matching the given name. + * + * Parameters: + * match - {String | Object} A layer name. The name can also be a regular + * expression literal or object. In addition, it can be any object + * with a method named test. For reqular expressions or other, if + * name.test(layer.name) evaluates to true, the layer will be included + * in the list of layers returned. If no layers are found, an empty + * array is returned. + * + * Returns: + * {Array()} A list of layers matching the given name. + * An empty array is returned if no matches are found. + */ + getLayersByName: function(match) { + return this.getLayersBy("name", match); + }, + + /** + * APIMethod: getLayersByClass + * Get a list of layers of a given class (CLASS_NAME). + * + * Parameters: + * match - {String | Object} A layer class name. The match can also be a + * regular expression literal or object. In addition, it can be any + * object with a method named test. For reqular expressions or other, + * if type.test(layer.CLASS_NAME) evaluates to true, the layer will + * be included in the list of layers returned. If no layers are + * found, an empty array is returned. + * + * Returns: + * {Array()} A list of layers matching the given class. + * An empty array is returned if no matches are found. + */ + getLayersByClass: function(match) { + return this.getLayersBy("CLASS_NAME", match); + }, + + /** + * APIMethod: getControlsBy + * Get a list of controls with properties matching the given criteria. + * + * Parameters: + * property - {String} A control property to be matched. + * match - {String | Object} A string to match. Can also be a regular + * expression literal or object. In addition, it can be any object + * with a method named test. For reqular expressions or other, if + * match.test(layer[property]) evaluates to true, the layer will be + * included in the array returned. If no layers are found, an empty + * array is returned. + * + * Returns: + * {Array()} A list of controls matching the given + * criteria. An empty array is returned if no matches are found. + */ + getControlsBy: function(property, match) { + return this.getBy("controls", property, match); + }, + + /** + * APIMethod: getControlsByClass + * Get a list of controls of a given class (CLASS_NAME). + * + * Parameters: + * match - {String | Object} A control class name. The match can also be a + * regular expression literal or object. In addition, it can be any + * object with a method named test. For reqular expressions or other, + * if type.test(control.CLASS_NAME) evaluates to true, the control will + * be included in the list of controls returned. If no controls are + * found, an empty array is returned. + * + * Returns: + * {Array()} A list of controls matching the given class. + * An empty array is returned if no matches are found. + */ + getControlsByClass: function(match) { + return this.getControlsBy("CLASS_NAME", match); + }, + + /********************************************************/ + /* */ + /* Layer Functions */ + /* */ + /* The following functions deal with adding and */ + /* removing Layers to and from the Map */ + /* */ + /********************************************************/ + + /** + * APIMethod: getLayer + * Get a layer based on its id + * + * Parameters: + * id - {String} A layer id + * + * Returns: + * {} The Layer with the corresponding id from the map's + * layer collection, or null if not found. + */ + getLayer: function(id) { + var foundLayer = null; + for (var i=0, len=this.layers.length; i} + * zIdx - {int} + */ + setLayerZIndex: function (layer, zIdx) { + layer.setZIndex( + this.Z_INDEX_BASE[layer.isBaseLayer ? 'BaseLayer' : 'Overlay'] + + zIdx * 5 ); + }, + + /** + * Method: resetLayersZIndex + * Reset each layer's z-index based on layer's array index + */ + resetLayersZIndex: function() { + for (var i=0, len=this.layers.length; i} + * + * Returns: + * {Boolean} True if the layer has been added to the map. + */ + addLayer: function (layer) { + for(var i = 0, len = this.layers.length; i < len; i++) { + if (this.layers[i] == layer) { + return false; + } + } + if (this.events.triggerEvent("preaddlayer", {layer: layer}) === false) { + return false; + } + if(this.allOverlays) { + layer.isBaseLayer = false; + } + + layer.div.className = "olLayerDiv"; + layer.div.style.overflow = ""; + this.setLayerZIndex(layer, this.layers.length); + + if (layer.isFixed) { + this.viewPortDiv.appendChild(layer.div); + } else { + this.layerContainerDiv.appendChild(layer.div); + } + this.layers.push(layer); + layer.setMap(this); + + if (layer.isBaseLayer || (this.allOverlays && !this.baseLayer)) { + if (this.baseLayer == null) { + // set the first baselaye we add as the baselayer + this.setBaseLayer(layer); + } else { + layer.setVisibility(false); + } + } else { + layer.redraw(); + } + + this.events.triggerEvent("addlayer", {layer: layer}); + layer.events.triggerEvent("added", {map: this, layer: layer}); + layer.afterAdd(); + + return true; + }, + + /** + * APIMethod: addLayers + * + * Parameters: + * layers - {Array()} + */ + addLayers: function (layers) { + for (var i=0, len=layers.length; i} + * setNewBaseLayer - {Boolean} Default is true + */ + removeLayer: function(layer, setNewBaseLayer) { + if (this.events.triggerEvent("preremovelayer", {layer: layer}) === false) { + return; + } + if (setNewBaseLayer == null) { + setNewBaseLayer = true; + } + + if (layer.isFixed) { + this.viewPortDiv.removeChild(layer.div); + } else { + this.layerContainerDiv.removeChild(layer.div); + } + OpenLayers.Util.removeItem(this.layers, layer); + layer.removeMap(this); + layer.map = null; + + // if we removed the base layer, need to set a new one + if(this.baseLayer == layer) { + this.baseLayer = null; + if(setNewBaseLayer) { + for(var i=0, len=this.layers.length; i} + * + * Returns: + * {Integer} The current (zero-based) index of the given layer in the map's + * layer stack. Returns -1 if the layer isn't on the map. + */ + getLayerIndex: function (layer) { + return OpenLayers.Util.indexOf(this.layers, layer); + }, + + /** + * APIMethod: setLayerIndex + * Move the given layer to the specified (zero-based) index in the layer + * list, changing its z-index in the map display. Use + * map.getLayerIndex() to find out the current index of a layer. Note + * that this cannot (or at least should not) be effectively used to + * raise base layers above overlays. + * + * Parameters: + * layer - {} + * idx - {int} + */ + setLayerIndex: function (layer, idx) { + var base = this.getLayerIndex(layer); + if (idx < 0) { + idx = 0; + } else if (idx > this.layers.length) { + idx = this.layers.length; + } + if (base != idx) { + this.layers.splice(base, 1); + this.layers.splice(idx, 0, layer); + for (var i=0, len=this.layers.length; i} + * delta - {int} + */ + raiseLayer: function (layer, delta) { + var idx = this.getLayerIndex(layer) + delta; + this.setLayerIndex(layer, idx); + }, + + /** + * APIMethod: setBaseLayer + * Allows user to specify one of the currently-loaded layers as the Map's + * new base layer. + * + * Parameters: + * newBaseLayer - {} + */ + setBaseLayer: function(newBaseLayer) { + + if (newBaseLayer != this.baseLayer) { + + // ensure newBaseLayer is already loaded + if (OpenLayers.Util.indexOf(this.layers, newBaseLayer) != -1) { + + // preserve center and scale when changing base layers + var center = this.getCachedCenter(); + var newResolution = OpenLayers.Util.getResolutionFromScale( + this.getScale(), newBaseLayer.units + ); + + // make the old base layer invisible + if (this.baseLayer != null && !this.allOverlays) { + this.baseLayer.setVisibility(false); + } + + // set new baselayer + this.baseLayer = newBaseLayer; + + if(!this.allOverlays || this.baseLayer.visibility) { + this.baseLayer.setVisibility(true); + // Layer may previously have been visible but not in range. + // In this case we need to redraw it to make it visible. + if (this.baseLayer.inRange === false) { + this.baseLayer.redraw(); + } + } + + // recenter the map + if (center != null) { + // new zoom level derived from old scale + var newZoom = this.getZoomForResolution( + newResolution || this.resolution, true + ); + // zoom and force zoom change + this.setCenter(center, newZoom, false, true); + } + + this.events.triggerEvent("changebaselayer", { + layer: this.baseLayer + }); + } + } + }, + + + /********************************************************/ + /* */ + /* Control Functions */ + /* */ + /* The following functions deal with adding and */ + /* removing Controls to and from the Map */ + /* */ + /********************************************************/ + + /** + * APIMethod: addControl + * Add the passed over control to the map. Optionally + * position the control at the given pixel. + * + * Parameters: + * control - {} + * px - {} + */ + addControl: function (control, px) { + this.controls.push(control); + this.addControlToMap(control, px); + }, + + /** + * APIMethod: addControls + * Add all of the passed over controls to the map. + * You can pass over an optional second array + * with pixel-objects to position the controls. + * The indices of the two arrays should match and + * you can add null as pixel for those controls + * you want to be autopositioned. + * + * Parameters: + * controls - {Array()} + * pixels - {Array()} + */ + addControls: function (controls, pixels) { + var pxs = (arguments.length === 1) ? [] : pixels; + for (var i=0, len=controls.length; i} + * px - {} + */ + addControlToMap: function (control, px) { + // If a control doesn't have a div at this point, it belongs in the + // viewport. + control.outsideViewport = (control.div != null); + + // If the map has a displayProjection, and the control doesn't, set + // the display projection. + if (this.displayProjection && !control.displayProjection) { + control.displayProjection = this.displayProjection; + } + + control.setMap(this); + var div = control.draw(px); + if (div) { + if(!control.outsideViewport) { + div.style.zIndex = this.Z_INDEX_BASE['Control'] + + this.controls.length; + this.viewPortDiv.appendChild( div ); + } + } + if(control.autoActivate) { + control.activate(); + } + }, + + /** + * APIMethod: getControl + * + * Parameters: + * id - {String} ID of the control to return. + * + * Returns: + * {} The control from the map's list of controls + * which has a matching 'id'. If none found, + * returns null. + */ + getControl: function (id) { + var returnControl = null; + for(var i=0, len=this.controls.length; i} The control to remove. + */ + removeControl: function (control) { + //make sure control is non-null and actually part of our map + if ( (control) && (control == this.getControl(control.id)) ) { + if (control.div && (control.div.parentNode == this.viewPortDiv)) { + this.viewPortDiv.removeChild(control.div); + } + OpenLayers.Util.removeItem(this.controls, control); + } + }, + + /********************************************************/ + /* */ + /* Popup Functions */ + /* */ + /* The following functions deal with adding and */ + /* removing Popups to and from the Map */ + /* */ + /********************************************************/ + + /** + * APIMethod: addPopup + * + * Parameters: + * popup - {} + * exclusive - {Boolean} If true, closes all other popups first + */ + addPopup: function(popup, exclusive) { + + if (exclusive) { + //remove all other popups from screen + for (var i = this.popups.length - 1; i >= 0; --i) { + this.removePopup(this.popups[i]); + } + } + + popup.map = this; + this.popups.push(popup); + var popupDiv = popup.draw(); + if (popupDiv) { + popupDiv.style.zIndex = this.Z_INDEX_BASE['Popup'] + + this.popups.length; + this.layerContainerDiv.appendChild(popupDiv); + } + }, + + /** + * APIMethod: removePopup + * + * Parameters: + * popup - {} + */ + removePopup: function(popup) { + OpenLayers.Util.removeItem(this.popups, popup); + if (popup.div) { + try { this.layerContainerDiv.removeChild(popup.div); } + catch (e) { } // Popups sometimes apparently get disconnected + // from the layerContainerDiv, and cause complaints. + } + popup.map = null; + }, + + /********************************************************/ + /* */ + /* Container Div Functions */ + /* */ + /* The following functions deal with the access to */ + /* and maintenance of the size of the container div */ + /* */ + /********************************************************/ + + /** + * APIMethod: getSize + * + * Returns: + * {} An object that represents the + * size, in pixels, of the div into which OpenLayers + * has been loaded. + * Note - A clone() of this locally cached variable is + * returned, so as not to allow users to modify it. + */ + getSize: function () { + var size = null; + if (this.size != null) { + size = this.size.clone(); + } + return size; + }, + + /** + * APIMethod: updateSize + * This function should be called by any external code which dynamically + * changes the size of the map div (because mozilla wont let us catch + * the "onresize" for an element) + */ + updateSize: function() { + // the div might have moved on the page, also + var newSize = this.getCurrentSize(); + if (newSize && !isNaN(newSize.h) && !isNaN(newSize.w)) { + this.events.clearMouseCache(); + var oldSize = this.getSize(); + if (oldSize == null) { + this.size = oldSize = newSize; + } + if (!newSize.equals(oldSize)) { + + // store the new size + this.size = newSize; + + //notify layers of mapresize + for(var i=0, len=this.layers.length; i} A new object with the dimensions + * of the map div + */ + getCurrentSize: function() { + + var size = new OpenLayers.Size(this.div.clientWidth, + this.div.clientHeight); + + if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) { + size.w = this.div.offsetWidth; + size.h = this.div.offsetHeight; + } + if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) { + size.w = parseInt(this.div.style.width); + size.h = parseInt(this.div.style.height); + } + return size; + }, + + /** + * Method: calculateBounds + * + * Parameters: + * center - {} Default is this.getCenter() + * resolution - {float} Default is this.getResolution() + * + * Returns: + * {} A bounds based on resolution, center, and + * current mapsize. + */ + calculateBounds: function(center, resolution) { + + var extent = null; + + if (center == null) { + center = this.getCachedCenter(); + } + if (resolution == null) { + resolution = this.getResolution(); + } + + if ((center != null) && (resolution != null)) { + var halfWDeg = (this.size.w * resolution) / 2; + var halfHDeg = (this.size.h * resolution) / 2; + + extent = new OpenLayers.Bounds(center.lon - halfWDeg, + center.lat - halfHDeg, + center.lon + halfWDeg, + center.lat + halfHDeg); + } + + return extent; + }, + + + /********************************************************/ + /* */ + /* Zoom, Center, Pan Functions */ + /* */ + /* The following functions handle the validation, */ + /* getting and setting of the Zoom Level and Center */ + /* as well as the panning of the Map */ + /* */ + /********************************************************/ + /** + * APIMethod: getCenter + * + * Returns: + * {} + */ + getCenter: function () { + var center = null; + var cachedCenter = this.getCachedCenter(); + if (cachedCenter) { + center = cachedCenter.clone(); + } + return center; + }, + + /** + * Method: getCachedCenter + * + * Returns: + * {} + */ + getCachedCenter: function() { + if (!this.center && this.size) { + this.center = this.getLonLatFromViewPortPx({ + x: this.size.w / 2, + y: this.size.h / 2 + }); + } + return this.center; + }, + + /** + * APIMethod: getZoom + * + * Returns: + * {Integer} + */ + getZoom: function () { + return this.zoom; + }, + + /** + * APIMethod: pan + * Allows user to pan by a value of screen pixels + * + * Parameters: + * dx - {Integer} + * dy - {Integer} + * options - {Object} Options to configure panning: + * - *animate* {Boolean} Use panTo instead of setCenter. Default is true. + * - *dragging* {Boolean} Call setCenter with dragging true. Default is + * false. + */ + pan: function(dx, dy, options) { + options = OpenLayers.Util.applyDefaults(options, { + animate: true, + dragging: false + }); + if (options.dragging) { + if (dx != 0 || dy != 0) { + this.moveByPx(dx, dy); + } + } else { + // getCenter + var centerPx = this.getViewPortPxFromLonLat(this.getCachedCenter()); + + // adjust + var newCenterPx = centerPx.add(dx, dy); + + if (this.dragging || !newCenterPx.equals(centerPx)) { + var newCenterLonLat = this.getLonLatFromViewPortPx(newCenterPx); + if (options.animate) { + this.panTo(newCenterLonLat); + } else { + this.moveTo(newCenterLonLat); + if(this.dragging) { + this.dragging = false; + this.events.triggerEvent("moveend"); + } + } + } + } + + }, + + /** + * APIMethod: panTo + * Allows user to pan to a new lonlat + * If the new lonlat is in the current extent the map will slide smoothly + * + * Parameters: + * lonlat - {} + */ + panTo: function(lonlat) { + if (this.panTween && this.getExtent().scale(this.panRatio).containsLonLat(lonlat)) { + var center = this.getCachedCenter(); + + // center will not change, don't do nothing + if (lonlat.equals(center)) { + return; + } + + var from = this.getPixelFromLonLat(center); + var to = this.getPixelFromLonLat(lonlat); + var vector = { x: to.x - from.x, y: to.y - from.y }; + var last = { x: 0, y: 0 }; + + this.panTween.start( { x: 0, y: 0 }, vector, this.panDuration, { + callbacks: { + eachStep: OpenLayers.Function.bind(function(px) { + var x = px.x - last.x, + y = px.y - last.y; + this.moveByPx(x, y); + last.x = Math.round(px.x); + last.y = Math.round(px.y); + }, this), + done: OpenLayers.Function.bind(function(px) { + this.moveTo(lonlat); + this.dragging = false; + this.events.triggerEvent("moveend"); + }, this) + } + }); + } else { + this.setCenter(lonlat); + } + }, + + /** + * APIMethod: setCenter + * Set the map center (and optionally, the zoom level). + * + * Parameters: + * lonlat - {|Array} The new center location. + * If provided as array, the first value is the x coordinate, + * and the 2nd value is the y coordinate. + * zoom - {Integer} Optional zoom level. + * dragging - {Boolean} Specifies whether or not to trigger + * movestart/end events + * forceZoomChange - {Boolean} Specifies whether or not to trigger zoom + * change events (needed on baseLayer change) + * + * TBD: reconsider forceZoomChange in 3.0 + */ + setCenter: function(lonlat, zoom, dragging, forceZoomChange) { + if (this.panTween) { + this.panTween.stop(); + } + if (this.zoomTween) { + this.zoomTween.stop(); + } + this.moveTo(lonlat, zoom, { + 'dragging': dragging, + 'forceZoomChange': forceZoomChange + }); + }, + + /** + * Method: moveByPx + * Drag the map by pixels. + * + * Parameters: + * dx - {Number} + * dy - {Number} + */ + moveByPx: function(dx, dy) { + var hw = this.size.w / 2; + var hh = this.size.h / 2; + var x = hw + dx; + var y = hh + dy; + var wrapDateLine = this.baseLayer.wrapDateLine; + var xRestriction = 0; + var yRestriction = 0; + if (this.restrictedExtent) { + xRestriction = hw; + yRestriction = hh; + // wrapping the date line makes no sense for restricted extents + wrapDateLine = false; + } + dx = wrapDateLine || + x <= this.maxPx.x - xRestriction && + x >= this.minPx.x + xRestriction ? Math.round(dx) : 0; + dy = y <= this.maxPx.y - yRestriction && + y >= this.minPx.y + yRestriction ? Math.round(dy) : 0; + if (dx || dy) { + if (!this.dragging) { + this.dragging = true; + this.events.triggerEvent("movestart"); + } + this.center = null; + if (dx) { + this.layerContainerOriginPx.x -= dx; + this.minPx.x -= dx; + this.maxPx.x -= dx; + } + if (dy) { + this.layerContainerOriginPx.y -= dy; + this.minPx.y -= dy; + this.maxPx.y -= dy; + } + this.applyTransform(); + var layer, i, len; + for (i=0, len=this.layers.length; i's maxExtent. + */ + adjustZoom: function(zoom) { + if (this.baseLayer && this.baseLayer.wrapDateLine) { + var resolution, resolutions = this.baseLayer.resolutions, + maxResolution = this.getMaxExtent().getWidth() / this.size.w; + if (this.getResolutionForZoom(zoom) > maxResolution) { + if (this.fractionalZoom) { + zoom = this.getZoomForResolution(maxResolution); + } else { + for (var i=zoom|0, ii=resolutions.length; i set to true, this will be the + * first zoom level that shows no more than one world width in the current + * map viewport. Components that rely on this value (e.g. zoom sliders) + * should also listen to the map's "updatesize" event and call this method + * in the "updatesize" listener. + * + * Returns: + * {Number} Minimum zoom level that shows a map not wider than its + * 's maxExtent. This is an Integer value, unless the map is + * configured with set to true. + */ + getMinZoom: function() { + return this.adjustZoom(0); + }, + + /** + * Method: moveTo + * + * Parameters: + * lonlat - {} + * zoom - {Integer} + * options - {Object} + */ + moveTo: function(lonlat, zoom, options) { + if (lonlat != null && !(lonlat instanceof OpenLayers.LonLat)) { + lonlat = new OpenLayers.LonLat(lonlat); + } + if (!options) { + options = {}; + } + if (zoom != null) { + zoom = parseFloat(zoom); + if (!this.fractionalZoom) { + zoom = Math.round(zoom); + } + } + var requestedZoom = zoom; + zoom = this.adjustZoom(zoom); + if (zoom !== requestedZoom) { + // zoom was adjusted, so keep old lonlat to avoid panning + lonlat = this.getCenter(); + } + // dragging is false by default + var dragging = options.dragging || this.dragging; + // forceZoomChange is false by default + var forceZoomChange = options.forceZoomChange; + + if (!this.getCachedCenter() && !this.isValidLonLat(lonlat)) { + lonlat = this.maxExtent.getCenterLonLat(); + this.center = lonlat.clone(); + } + + if(this.restrictedExtent != null) { + // In 3.0, decide if we want to change interpretation of maxExtent. + if(lonlat == null) { + lonlat = this.center; + } + if(zoom == null) { + zoom = this.getZoom(); + } + var resolution = this.getResolutionForZoom(zoom); + var extent = this.calculateBounds(lonlat, resolution); + if(!this.restrictedExtent.containsBounds(extent)) { + var maxCenter = this.restrictedExtent.getCenterLonLat(); + if(extent.getWidth() > this.restrictedExtent.getWidth()) { + lonlat = new OpenLayers.LonLat(maxCenter.lon, lonlat.lat); + } else if(extent.left < this.restrictedExtent.left) { + lonlat = lonlat.add(this.restrictedExtent.left - + extent.left, 0); + } else if(extent.right > this.restrictedExtent.right) { + lonlat = lonlat.add(this.restrictedExtent.right - + extent.right, 0); + } + if(extent.getHeight() > this.restrictedExtent.getHeight()) { + lonlat = new OpenLayers.LonLat(lonlat.lon, maxCenter.lat); + } else if(extent.bottom < this.restrictedExtent.bottom) { + lonlat = lonlat.add(0, this.restrictedExtent.bottom - + extent.bottom); + } + else if(extent.top > this.restrictedExtent.top) { + lonlat = lonlat.add(0, this.restrictedExtent.top - + extent.top); + } + } + } + + var zoomChanged = forceZoomChange || ( + (this.isValidZoomLevel(zoom)) && + (zoom != this.getZoom()) ); + + var centerChanged = (this.isValidLonLat(lonlat)) && + (!lonlat.equals(this.center)); + + // if neither center nor zoom will change, no need to do anything + if (zoomChanged || centerChanged || dragging) { + dragging || this.events.triggerEvent("movestart", { + zoomChanged: zoomChanged + }); + + if (centerChanged) { + if (!zoomChanged && this.center) { + // if zoom hasnt changed, just slide layerContainer + // (must be done before setting this.center to new value) + this.centerLayerContainer(lonlat); + } + this.center = lonlat.clone(); + } + + var res = zoomChanged ? + this.getResolutionForZoom(zoom) : this.getResolution(); + // (re)set the layerContainerDiv's location + if (zoomChanged || this.layerContainerOrigin == null) { + this.layerContainerOrigin = this.getCachedCenter(); + this.layerContainerOriginPx.x = 0; + this.layerContainerOriginPx.y = 0; + this.applyTransform(); + var maxExtent = this.getMaxExtent({restricted: true}); + var maxExtentCenter = maxExtent.getCenterLonLat(); + var lonDelta = this.center.lon - maxExtentCenter.lon; + var latDelta = maxExtentCenter.lat - this.center.lat; + var extentWidth = Math.round(maxExtent.getWidth() / res); + var extentHeight = Math.round(maxExtent.getHeight() / res); + this.minPx = { + x: (this.size.w - extentWidth) / 2 - lonDelta / res, + y: (this.size.h - extentHeight) / 2 - latDelta / res + }; + this.maxPx = { + x: this.minPx.x + Math.round(maxExtent.getWidth() / res), + y: this.minPx.y + Math.round(maxExtent.getHeight() / res) + }; + } + + if (zoomChanged) { + this.zoom = zoom; + this.resolution = res; + } + + var bounds = this.getExtent(); + + //send the move call to the baselayer and all the overlays + + if(this.baseLayer.visibility) { + this.baseLayer.moveTo(bounds, zoomChanged, options.dragging); + options.dragging || this.baseLayer.events.triggerEvent( + "moveend", {zoomChanged: zoomChanged} + ); + } + + bounds = this.baseLayer.getExtent(); + + for (var i=this.layers.length-1; i>=0; --i) { + var layer = this.layers[i]; + if (layer !== this.baseLayer && !layer.isBaseLayer) { + var inRange = layer.calculateInRange(); + if (layer.inRange != inRange) { + // the inRange property has changed. If the layer is + // no longer in range, we turn it off right away. If + // the layer is no longer out of range, the moveTo + // call below will turn on the layer. + layer.inRange = inRange; + if (!inRange) { + layer.display(false); + } + this.events.triggerEvent("changelayer", { + layer: layer, property: "visibility" + }); + } + if (inRange && layer.visibility) { + layer.moveTo(bounds, zoomChanged, options.dragging); + options.dragging || layer.events.triggerEvent( + "moveend", {zoomChanged: zoomChanged} + ); + } + } + } + + this.events.triggerEvent("move"); + dragging || this.events.triggerEvent("moveend"); + + if (zoomChanged) { + //redraw popups + for (var i=0, len=this.popups.length; i} + */ + centerLayerContainer: function (lonlat) { + var originPx = this.getViewPortPxFromLonLat(this.layerContainerOrigin); + var newPx = this.getViewPortPxFromLonLat(lonlat); + + if ((originPx != null) && (newPx != null)) { + var oldLeft = this.layerContainerOriginPx.x; + var oldTop = this.layerContainerOriginPx.y; + var newLeft = Math.round(originPx.x - newPx.x); + var newTop = Math.round(originPx.y - newPx.y); + this.applyTransform( + (this.layerContainerOriginPx.x = newLeft), + (this.layerContainerOriginPx.y = newTop)); + var dx = oldLeft - newLeft; + var dy = oldTop - newTop; + this.minPx.x -= dx; + this.maxPx.x -= dx; + this.minPx.y -= dy; + this.maxPx.y -= dy; + } + }, + + /** + * Method: isValidZoomLevel + * + * Parameters: + * zoomLevel - {Integer} + * + * Returns: + * {Boolean} Whether or not the zoom level passed in is non-null and + * within the min/max range of zoom levels. + */ + isValidZoomLevel: function(zoomLevel) { + return ( (zoomLevel != null) && + (zoomLevel >= 0) && + (zoomLevel < this.getNumZoomLevels()) ); + }, + + /** + * Method: isValidLonLat + * + * Parameters: + * lonlat - {} + * + * Returns: + * {Boolean} Whether or not the lonlat passed in is non-null and within + * the maxExtent bounds + */ + isValidLonLat: function(lonlat) { + var valid = false; + if (lonlat != null) { + var maxExtent = this.getMaxExtent(); + var worldBounds = this.baseLayer.wrapDateLine && maxExtent; + valid = maxExtent.containsLonLat(lonlat, {worldBounds: worldBounds}); + } + return valid; + }, + + /********************************************************/ + /* */ + /* Layer Options */ + /* */ + /* Accessor functions to Layer Options parameters */ + /* */ + /********************************************************/ + + /** + * APIMethod: getProjection + * This method returns a string representing the projection. In + * the case of projection support, this will be the srsCode which + * is loaded -- otherwise it will simply be the string value that + * was passed to the projection at startup. + * + * FIXME: In 3.0, we will remove getProjectionObject, and instead + * return a Projection object from this function. + * + * Returns: + * {String} The Projection string from the base layer or null. + */ + getProjection: function() { + var projection = this.getProjectionObject(); + return projection ? projection.getCode() : null; + }, + + /** + * APIMethod: getProjectionObject + * Returns the projection obect from the baselayer. + * + * Returns: + * {} The Projection of the base layer. + */ + getProjectionObject: function() { + var projection = null; + if (this.baseLayer != null) { + projection = this.baseLayer.projection; + } + return projection; + }, + + /** + * APIMethod: getMaxResolution + * + * Returns: + * {String} The Map's Maximum Resolution + */ + getMaxResolution: function() { + var maxResolution = null; + if (this.baseLayer != null) { + maxResolution = this.baseLayer.maxResolution; + } + return maxResolution; + }, + + /** + * APIMethod: getMaxExtent + * + * Parameters: + * options - {Object} + * + * Allowed Options: + * restricted - {Boolean} If true, returns restricted extent (if it is + * available.) + * + * Returns: + * {} The maxExtent property as set on the current + * baselayer, unless the 'restricted' option is set, in which case + * the 'restrictedExtent' option from the map is returned (if it + * is set). + */ + getMaxExtent: function (options) { + var maxExtent = null; + if(options && options.restricted && this.restrictedExtent){ + maxExtent = this.restrictedExtent; + } else if (this.baseLayer != null) { + maxExtent = this.baseLayer.maxExtent; + } + return maxExtent; + }, + + /** + * APIMethod: getNumZoomLevels + * + * Returns: + * {Integer} The total number of zoom levels that can be displayed by the + * current baseLayer. + */ + getNumZoomLevels: function() { + var numZoomLevels = null; + if (this.baseLayer != null) { + numZoomLevels = this.baseLayer.numZoomLevels; + } + return numZoomLevels; + }, + + /********************************************************/ + /* */ + /* Baselayer Functions */ + /* */ + /* The following functions, all publicly exposed */ + /* in the API?, are all merely wrappers to the */ + /* the same calls on whatever layer is set as */ + /* the current base layer */ + /* */ + /********************************************************/ + + /** + * APIMethod: getExtent + * + * Returns: + * {} A Bounds object which represents the lon/lat + * bounds of the current viewPort. + * If no baselayer is set, returns null. + */ + getExtent: function () { + var extent = null; + if (this.baseLayer != null) { + extent = this.baseLayer.getExtent(); + } + return extent; + }, + + /** + * APIMethod: getResolution + * + * Returns: + * {Float} The current resolution of the map. + * If no baselayer is set, returns null. + */ + getResolution: function () { + var resolution = null; + if (this.baseLayer != null) { + resolution = this.baseLayer.getResolution(); + } else if(this.allOverlays === true && this.layers.length > 0) { + // while adding the 1st layer to the map in allOverlays mode, + // this.baseLayer is not set yet when we need the resolution + // for calculateInRange. + resolution = this.layers[0].getResolution(); + } + return resolution; + }, + + /** + * APIMethod: getUnits + * + * Returns: + * {Float} The current units of the map. + * If no baselayer is set, returns null. + */ + getUnits: function () { + var units = null; + if (this.baseLayer != null) { + units = this.baseLayer.units; + } + return units; + }, + + /** + * APIMethod: getScale + * + * Returns: + * {Float} The current scale denominator of the map. + * If no baselayer is set, returns null. + */ + getScale: function () { + var scale = null; + if (this.baseLayer != null) { + var res = this.getResolution(); + var units = this.baseLayer.units; + scale = OpenLayers.Util.getScaleFromResolution(res, units); + } + return scale; + }, + + + /** + * APIMethod: getZoomForExtent + * + * Parameters: + * bounds - {} + * closest - {Boolean} Find the zoom level that most closely fits the + * specified bounds. Note that this may result in a zoom that does + * not exactly contain the entire extent. + * Default is false. + * + * Returns: + * {Integer} A suitable zoom level for the specified bounds. + * If no baselayer is set, returns null. + */ + getZoomForExtent: function (bounds, closest) { + var zoom = null; + if (this.baseLayer != null) { + zoom = this.baseLayer.getZoomForExtent(bounds, closest); + } + return zoom; + }, + + /** + * APIMethod: getResolutionForZoom + * + * Parameters: + * zoom - {Float} + * + * Returns: + * {Float} A suitable resolution for the specified zoom. If no baselayer + * is set, returns null. + */ + getResolutionForZoom: function(zoom) { + var resolution = null; + if(this.baseLayer) { + resolution = this.baseLayer.getResolutionForZoom(zoom); + } + return resolution; + }, + + /** + * APIMethod: getZoomForResolution + * + * Parameters: + * resolution - {Float} + * closest - {Boolean} Find the zoom level that corresponds to the absolute + * closest resolution, which may result in a zoom whose corresponding + * resolution is actually smaller than we would have desired (if this + * is being called from a getZoomForExtent() call, then this means that + * the returned zoom index might not actually contain the entire + * extent specified... but it'll be close). + * Default is false. + * + * Returns: + * {Integer} A suitable zoom level for the specified resolution. + * If no baselayer is set, returns null. + */ + getZoomForResolution: function(resolution, closest) { + var zoom = null; + if (this.baseLayer != null) { + zoom = this.baseLayer.getZoomForResolution(resolution, closest); + } + return zoom; + }, + + /********************************************************/ + /* */ + /* Zooming Functions */ + /* */ + /* The following functions, all publicly exposed */ + /* in the API, are all merely wrappers to the */ + /* the setCenter() function */ + /* */ + /********************************************************/ + + /** + * APIMethod: zoomTo + * Zoom to a specific zoom level. Zooming will be animated unless the map + * is configured with {zoomMethod: null}. To zoom without animation, use + * without a lonlat argument. + * + * Parameters: + * zoom - {Integer} + */ + zoomTo: function(zoom, xy) { + // non-API arguments: + // xy - {} optional zoom origin + + var map = this; + if (map.isValidZoomLevel(zoom)) { + if (map.baseLayer.wrapDateLine) { + zoom = map.adjustZoom(zoom); + } + if (map.zoomTween) { + var currentRes = map.getResolution(), + targetRes = map.getResolutionForZoom(zoom), + start = {scale: 1}, + end = {scale: currentRes / targetRes}; + if (map.zoomTween.playing && map.zoomTween.duration < 3 * map.zoomDuration) { + // update the end scale, and reuse the running zoomTween + map.zoomTween.finish = { + scale: map.zoomTween.finish.scale * end.scale + }; + } else { + if (!xy) { + var size = map.getSize(); + xy = {x: size.w / 2, y: size.h / 2}; + } + map.zoomTween.start(start, end, map.zoomDuration, { + minFrameRate: 50, // don't spend much time zooming + callbacks: { + eachStep: function(data) { + var containerOrigin = map.layerContainerOriginPx, + scale = data.scale, + dx = ((scale - 1) * (containerOrigin.x - xy.x)) | 0, + dy = ((scale - 1) * (containerOrigin.y - xy.y)) | 0; + map.applyTransform(containerOrigin.x + dx, containerOrigin.y + dy, scale); + }, + done: function(data) { + map.applyTransform(); + var resolution = map.getResolution() / data.scale, + zoom = map.getZoomForResolution(resolution, true) + map.moveTo(map.getZoomTargetCenter(xy, resolution), zoom, true); + } + } + }); + } + } else { + var center = xy ? + map.getZoomTargetCenter(xy, map.getResolutionForZoom(zoom)) : + null; + map.setCenter(center, zoom); + } + } + }, + + /** + * APIMethod: zoomIn + * + */ + zoomIn: function() { + this.zoomTo(this.getZoom() + 1); + }, + + /** + * APIMethod: zoomOut + * + */ + zoomOut: function() { + this.zoomTo(this.getZoom() - 1); + }, + + /** + * APIMethod: zoomToExtent + * Zoom to the passed in bounds, recenter + * + * Parameters: + * bounds - {|Array} If provided as an array, the array + * should consist of four values (left, bottom, right, top). + * closest - {Boolean} Find the zoom level that most closely fits the + * specified bounds. Note that this may result in a zoom that does + * not exactly contain the entire extent. + * Default is false. + * + */ + zoomToExtent: function(bounds, closest) { + if (!(bounds instanceof OpenLayers.Bounds)) { + bounds = new OpenLayers.Bounds(bounds); + } + var center = bounds.getCenterLonLat(); + if (this.baseLayer.wrapDateLine) { + var maxExtent = this.getMaxExtent(); + + //fix straddling bounds (in the case of a bbox that straddles the + // dateline, it's left and right boundaries will appear backwards. + // we fix this by allowing a right value that is greater than the + // max value at the dateline -- this allows us to pass a valid + // bounds to calculate zoom) + // + bounds = bounds.clone(); + while (bounds.right < bounds.left) { + bounds.right += maxExtent.getWidth(); + } + //if the bounds was straddling (see above), then the center point + // we got from it was wrong. So we take our new bounds and ask it + // for the center. + // + center = bounds.getCenterLonLat().wrapDateLine(maxExtent); + } + this.setCenter(center, this.getZoomForExtent(bounds, closest)); + }, + + /** + * APIMethod: zoomToMaxExtent + * Zoom to the full extent and recenter. + * + * Parameters: + * options - {Object} + * + * Allowed Options: + * restricted - {Boolean} True to zoom to restricted extent if it is + * set. Defaults to true. + */ + zoomToMaxExtent: function(options) { + //restricted is true by default + var restricted = (options) ? options.restricted : true; + + var maxExtent = this.getMaxExtent({ + 'restricted': restricted + }); + this.zoomToExtent(maxExtent); + }, + + /** + * APIMethod: zoomToScale + * Zoom to a specified scale + * + * Parameters: + * scale - {float} + * closest - {Boolean} Find the zoom level that most closely fits the + * specified scale. Note that this may result in a zoom that does + * not exactly contain the entire extent. + * Default is false. + * + */ + zoomToScale: function(scale, closest) { + var res = OpenLayers.Util.getResolutionFromScale(scale, + this.baseLayer.units); + + var halfWDeg = (this.size.w * res) / 2; + var halfHDeg = (this.size.h * res) / 2; + var center = this.getCachedCenter(); + + var extent = new OpenLayers.Bounds(center.lon - halfWDeg, + center.lat - halfHDeg, + center.lon + halfWDeg, + center.lat + halfHDeg); + this.zoomToExtent(extent, closest); + }, + + /********************************************************/ + /* */ + /* Translation Functions */ + /* */ + /* The following functions translate between */ + /* LonLat, LayerPx, and ViewPortPx */ + /* */ + /********************************************************/ + + // + // TRANSLATION: LonLat <-> ViewPortPx + // + + /** + * Method: getLonLatFromViewPortPx + * + * Parameters: + * viewPortPx - {|Object} An OpenLayers.Pixel or + * an object with a 'x' + * and 'y' properties. + * + * Returns: + * {} An OpenLayers.LonLat which is the passed-in view + * port , translated into lon/lat + * by the current base layer. + */ + getLonLatFromViewPortPx: function (viewPortPx) { + var lonlat = null; + if (this.baseLayer != null) { + lonlat = this.baseLayer.getLonLatFromViewPortPx(viewPortPx); + } + return lonlat; + }, + + /** + * APIMethod: getViewPortPxFromLonLat + * + * Parameters: + * lonlat - {} + * + * Returns: + * {} An OpenLayers.Pixel which is the passed-in + * , translated into view port + * pixels by the current base layer. + */ + getViewPortPxFromLonLat: function (lonlat) { + var px = null; + if (this.baseLayer != null) { + px = this.baseLayer.getViewPortPxFromLonLat(lonlat); + } + return px; + }, + + /** + * Method: getZoomTargetCenter + * + * Parameters: + * xy - {} The zoom origin pixel location on the screen + * resolution - {Float} The resolution we want to get the center for + * + * Returns: + * {} The location of the map center after the + * transformation described by the origin xy and the target resolution. + */ + getZoomTargetCenter: function (xy, resolution) { + var lonlat = null, + size = this.getSize(), + deltaX = size.w/2 - xy.x, + deltaY = xy.y - size.h/2, + zoomPoint = this.getLonLatFromPixel(xy); + if (zoomPoint) { + lonlat = new OpenLayers.LonLat( + zoomPoint.lon + deltaX * resolution, + zoomPoint.lat + deltaY * resolution + ); + } + return lonlat; + }, + + // + // CONVENIENCE TRANSLATION FUNCTIONS FOR API + // + + /** + * APIMethod: getLonLatFromPixel + * + * Parameters: + * px - {|Object} An OpenLayers.Pixel or an object with + * a 'x' and 'y' properties. + * + * Returns: + * {} An OpenLayers.LonLat corresponding to the given + * OpenLayers.Pixel, translated into lon/lat by the + * current base layer + */ + getLonLatFromPixel: function (px) { + return this.getLonLatFromViewPortPx(px); + }, + + /** + * APIMethod: getPixelFromLonLat + * Returns a pixel location given a map location. The map location is + * translated to an integer pixel location (in viewport pixel + * coordinates) by the current base layer. + * + * Parameters: + * lonlat - {} A map location. + * + * Returns: + * {} An OpenLayers.Pixel corresponding to the + * translated into view port pixels by the current + * base layer. + */ + getPixelFromLonLat: function (lonlat) { + var px = this.getViewPortPxFromLonLat(lonlat); + px.x = Math.round(px.x); + px.y = Math.round(px.y); + return px; + }, + + /** + * Method: getGeodesicPixelSize + * + * Parameters: + * px - {} The pixel to get the geodesic length for. If + * not provided, the center pixel of the map viewport will be used. + * + * Returns: + * {} The geodesic size of the pixel in kilometers. + */ + getGeodesicPixelSize: function(px) { + var lonlat = px ? this.getLonLatFromPixel(px) : ( + this.getCachedCenter() || new OpenLayers.LonLat(0, 0)); + var res = this.getResolution(); + var left = lonlat.add(-res / 2, 0); + var right = lonlat.add(res / 2, 0); + var bottom = lonlat.add(0, -res / 2); + var top = lonlat.add(0, res / 2); + var dest = new OpenLayers.Projection("EPSG:4326"); + var source = this.getProjectionObject() || dest; + if(!source.equals(dest)) { + left.transform(source, dest); + right.transform(source, dest); + bottom.transform(source, dest); + top.transform(source, dest); + } + + return new OpenLayers.Size( + OpenLayers.Util.distVincenty(left, right), + OpenLayers.Util.distVincenty(bottom, top) + ); + }, + + + + // + // TRANSLATION: ViewPortPx <-> LayerPx + // + + /** + * APIMethod: getViewPortPxFromLayerPx + * + * Parameters: + * layerPx - {} + * + * Returns: + * {} Layer Pixel translated into ViewPort Pixel + * coordinates + */ + getViewPortPxFromLayerPx:function(layerPx) { + var viewPortPx = null; + if (layerPx != null) { + var dX = this.layerContainerOriginPx.x; + var dY = this.layerContainerOriginPx.y; + viewPortPx = layerPx.add(dX, dY); + } + return viewPortPx; + }, + + /** + * APIMethod: getLayerPxFromViewPortPx + * + * Parameters: + * viewPortPx - {} + * + * Returns: + * {} ViewPort Pixel translated into Layer Pixel + * coordinates + */ + getLayerPxFromViewPortPx:function(viewPortPx) { + var layerPx = null; + if (viewPortPx != null) { + var dX = -this.layerContainerOriginPx.x; + var dY = -this.layerContainerOriginPx.y; + layerPx = viewPortPx.add(dX, dY); + if (isNaN(layerPx.x) || isNaN(layerPx.y)) { + layerPx = null; + } + } + return layerPx; + }, + + // + // TRANSLATION: LonLat <-> LayerPx + // + + /** + * Method: getLonLatFromLayerPx + * + * Parameters: + * px - {} + * + * Returns: + * {} + */ + getLonLatFromLayerPx: function (px) { + //adjust for displacement of layerContainerDiv + px = this.getViewPortPxFromLayerPx(px); + return this.getLonLatFromViewPortPx(px); + }, + + /** + * APIMethod: getLayerPxFromLonLat + * + * Parameters: + * lonlat - {} lonlat + * + * Returns: + * {} An OpenLayers.Pixel which is the passed-in + * , translated into layer pixels + * by the current base layer + */ + getLayerPxFromLonLat: function (lonlat) { + //adjust for displacement of layerContainerDiv + var px = this.getPixelFromLonLat(lonlat); + return this.getLayerPxFromViewPortPx(px); + }, + + /** + * Method: applyTransform + * Applies the given transform to the . This method has + * a 2-stage fallback from translate3d/scale3d via translate/scale to plain + * style.left/style.top, in which case no scaling is supported. + * + * Parameters: + * x - {Number} x parameter for the translation. Defaults to the x value of + * the map's + * y - {Number} y parameter for the translation. Defaults to the y value of + * the map's + * scale - {Number} scale. Defaults to 1 if not provided. + */ + applyTransform: function(x, y, scale) { + scale = scale || 1; + var origin = this.layerContainerOriginPx, + needTransform = scale !== 1; + x = x || origin.x; + y = y || origin.y; + + var style = this.layerContainerDiv.style, + transform = this.applyTransform.transform, + template = this.applyTransform.template; + + if (transform === undefined) { + transform = OpenLayers.Util.vendorPrefix.style('transform'); + this.applyTransform.transform = transform; + if (transform) { + // Try translate3d, but only if the viewPortDiv has a transform + // defined in a stylesheet + var computedStyle = OpenLayers.Element.getStyle(this.viewPortDiv, + OpenLayers.Util.vendorPrefix.css('transform')); + if (!computedStyle || computedStyle !== 'none') { + template = ['translate3d(', ',0) ', 'scale3d(', ',1)']; + style[transform] = [template[0], '0,0', template[1]].join(''); + } + // If no transform is defined in the stylesheet or translate3d + // does not stick, use translate and scale + if (!template || !~style[transform].indexOf(template[0])) { + template = ['translate(', ') ', 'scale(', ')']; + } + this.applyTransform.template = template; + } + } + + // If we do 3d transforms, we always want to use them. If we do 2d + // transforms, we only use them when we need to. + if (transform !== null && (template[0] === 'translate3d(' || needTransform === true)) { + // Our 2d transforms are combined with style.left and style.top, so + // adjust x and y values and set the origin as left and top + if (needTransform === true && template[0] === 'translate(') { + x -= origin.x; + y -= origin.y; + style.left = origin.x + 'px'; + style.top = origin.y + 'px'; + } + style[transform] = [ + template[0], x, 'px,', y, 'px', template[1], + template[2], scale, ',', scale, template[3] + ].join(''); + } else { + style.left = x + 'px'; + style.top = y + 'px'; + // We previously might have had needTransform, so remove transform + if (transform !== null) { + style[transform] = ''; + } + } + }, + + CLASS_NAME: "OpenLayers.Map" +}); + +/** + * Constant: TILE_WIDTH + * {Integer} 256 Default tile width (unless otherwise specified) + */ +OpenLayers.Map.TILE_WIDTH = 256; +/** + * Constant: TILE_HEIGHT + * {Integer} 256 Default tile height (unless otherwise specified) + */ +OpenLayers.Map.TILE_HEIGHT = 256; +/* ====================================================================== + OpenLayers/Layer.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/BaseTypes/Class.js + * @requires OpenLayers/Map.js + * @requires OpenLayers/Projection.js + */ + +/** + * Class: OpenLayers.Layer + */ +OpenLayers.Layer = OpenLayers.Class({ + + /** + * APIProperty: id + * {String} + */ + id: null, + + /** + * APIProperty: name + * {String} + */ + name: null, + + /** + * APIProperty: div + * {DOMElement} + */ + div: null, + + /** + * APIProperty: opacity + * {Float} The layer's opacity. Float number between 0.0 and 1.0. Default + * is 1. + */ + opacity: 1, + + /** + * APIProperty: alwaysInRange + * {Boolean} If a layer's display should not be scale-based, this should + * be set to true. This will cause the layer, as an overlay, to always + * be 'active', by always returning true from the calculateInRange() + * function. + * + * If not explicitly specified for a layer, its value will be + * determined on startup in initResolutions() based on whether or not + * any scale-specific properties have been set as options on the + * layer. If no scale-specific options have been set on the layer, we + * assume that it should always be in range. + * + * See #987 for more info. + */ + alwaysInRange: null, + + /** + * Constant: RESOLUTION_PROPERTIES + * {Array} The properties that are used for calculating resolutions + * information. + */ + RESOLUTION_PROPERTIES: [ + 'scales', 'resolutions', + 'maxScale', 'minScale', + 'maxResolution', 'minResolution', + 'numZoomLevels', 'maxZoomLevel' + ], + + /** + * APIProperty: events + * {} + * + * Register a listener for a particular event with the following syntax: + * (code) + * layer.events.register(type, obj, listener); + * (end) + * + * Listeners will be called with a reference to an event object. The + * properties of this event depends on exactly what happened. + * + * All event objects have at least the following properties: + * object - {Object} A reference to layer.events.object. + * element - {DOMElement} A reference to layer.events.element. + * + * Supported map event types: + * loadstart - Triggered when layer loading starts. When using a Vector + * layer with a Fixed or BBOX strategy, the event object includes + * a *filter* property holding the OpenLayers.Filter used when + * calling read on the protocol. + * loadend - Triggered when layer loading ends. When using a Vector layer + * with a Fixed or BBOX strategy, the event object includes a + * *response* property holding an OpenLayers.Protocol.Response object. + * visibilitychanged - Triggered when the layer's visibility property is + * changed, e.g. by turning the layer on or off in the layer switcher. + * Note that the actual visibility of the layer can also change if it + * gets out of range (see ). If you also want to catch + * these cases, register for the map's 'changelayer' event instead. + * move - Triggered when layer moves (triggered with every mousemove + * during a drag). + * moveend - Triggered when layer is done moving, object passed as + * argument has a zoomChanged boolean property which tells that the + * zoom has changed. + * added - Triggered after the layer is added to a map. Listeners will + * receive an object with a *map* property referencing the map and a + * *layer* property referencing the layer. + * removed - Triggered after the layer is removed from the map. Listeners + * will receive an object with a *map* property referencing the map and + * a *layer* property referencing the layer. + */ + events: null, + + /** + * APIProperty: map + * {} This variable is set when the layer is added to + * the map, via the accessor function setMap(). + */ + map: null, + + /** + * APIProperty: isBaseLayer + * {Boolean} Whether or not the layer is a base layer. This should be set + * individually by all subclasses. Default is false + */ + isBaseLayer: false, + + /** + * Property: alpha + * {Boolean} The layer's images have an alpha channel. Default is false. + */ + alpha: false, + + /** + * APIProperty: displayInLayerSwitcher + * {Boolean} Display the layer's name in the layer switcher. Default is + * true. + */ + displayInLayerSwitcher: true, + + /** + * APIProperty: visibility + * {Boolean} The layer should be displayed in the map. Default is true. + */ + visibility: true, + + /** + * APIProperty: attribution + * {String} Attribution string, displayed when an + * has been added to the map. + */ + attribution: null, + + /** + * Property: inRange + * {Boolean} The current map resolution is within the layer's min/max + * range. This is set in whenever the zoom + * changes. + */ + inRange: false, + + /** + * Propery: imageSize + * {} For layers with a gutter, the image is larger than + * the tile by twice the gutter in each dimension. + */ + imageSize: null, + + // OPTIONS + + /** + * Property: options + * {Object} An optional object whose properties will be set on the layer. + * Any of the layer properties can be set as a property of the options + * object and sent to the constructor when the layer is created. + */ + options: null, + + /** + * APIProperty: eventListeners + * {Object} If set as an option at construction, the eventListeners + * object will be registered with . Object + * structure must be a listeners object as shown in the example for + * the events.on method. + */ + eventListeners: null, + + /** + * APIProperty: gutter + * {Integer} Determines the width (in pixels) of the gutter around image + * tiles to ignore. By setting this property to a non-zero value, + * images will be requested that are wider and taller than the tile + * size by a value of 2 x gutter. This allows artifacts of rendering + * at tile edges to be ignored. Set a gutter value that is equal to + * half the size of the widest symbol that needs to be displayed. + * Defaults to zero. Non-tiled layers always have zero gutter. + */ + gutter: 0, + + /** + * APIProperty: projection + * {} or {} Specifies the projection of the layer. + * Can be set in the layer options. If not specified in the layer options, + * it is set to the default projection specified in the map, + * when the layer is added to the map. + * Projection along with default maxExtent and resolutions + * are set automatically with commercial baselayers in EPSG:3857, + * such as Google, Bing and OpenStreetMap, and do not need to be specified. + * Otherwise, if specifying projection, also set maxExtent, + * maxResolution or resolutions as appropriate. + * When using vector layers with strategies, layer projection should be set + * to the projection of the source data if that is different from the map default. + * + * Can be either a string or an object; + * if a string is passed, will be converted to an object when + * the layer is added to the map. + * + */ + projection: null, + + /** + * APIProperty: units + * {String} The layer map units. Defaults to null. Possible values + * are 'degrees' (or 'dd'), 'm', 'ft', 'km', 'mi', 'inches'. + * Normally taken from the projection. + * Only required if both map and layers do not define a projection, + * or if they define a projection which does not define units. + */ + units: null, + + /** + * APIProperty: scales + * {Array} An array of map scales in descending order. The values in the + * array correspond to the map scale denominator. Note that these + * values only make sense if the display (monitor) resolution of the + * client is correctly guessed by whomever is configuring the + * application. In addition, the units property must also be set. + * Use instead wherever possible. + */ + scales: null, + + /** + * APIProperty: resolutions + * {Array} A list of map resolutions (map units per pixel) in descending + * order. If this is not set in the layer constructor, it will be set + * based on other resolution related properties (maxExtent, + * maxResolution, maxScale, etc.). + */ + resolutions: null, + + /** + * APIProperty: maxExtent + * {|Array} If provided as an array, the array + * should consist of four values (left, bottom, right, top). + * The maximum extent for the layer. Defaults to null. + * + * The center of these bounds will not stray outside + * of the viewport extent during panning. In addition, if + * is set to false, data will not be + * requested that falls completely outside of these bounds. + */ + maxExtent: null, + + /** + * APIProperty: minExtent + * {|Array} If provided as an array, the array + * should consist of four values (left, bottom, right, top). + * The minimum extent for the layer. Defaults to null. + */ + minExtent: null, + + /** + * APIProperty: maxResolution + * {Float} Default max is 360 deg / 256 px, which corresponds to + * zoom level 0 on gmaps. Specify a different value in the layer + * options if you are not using the default + * and displaying the whole world. + */ + maxResolution: null, + + /** + * APIProperty: minResolution + * {Float} + */ + minResolution: null, + + /** + * APIProperty: numZoomLevels + * {Integer} + */ + numZoomLevels: null, + + /** + * APIProperty: minScale + * {Float} + */ + minScale: null, + + /** + * APIProperty: maxScale + * {Float} + */ + maxScale: null, + + /** + * APIProperty: displayOutsideMaxExtent + * {Boolean} Request map tiles that are completely outside of the max + * extent for this layer. Defaults to false. + */ + displayOutsideMaxExtent: false, + + /** + * APIProperty: wrapDateLine + * {Boolean} Wraps the world at the international dateline, so the map can + * be panned infinitely in longitudinal direction. Only use this on the + * base layer, and only if the layer's maxExtent equals the world bounds. + * #487 for more info. + */ + wrapDateLine: false, + + /** + * Property: metadata + * {Object} This object can be used to store additional information on a + * layer object. + */ + metadata: null, + + /** + * Constructor: OpenLayers.Layer + * + * Parameters: + * name - {String} The layer name + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function(name, options) { + + this.metadata = {}; + + options = OpenLayers.Util.extend({}, options); + // make sure we respect alwaysInRange if set on the prototype + if (this.alwaysInRange != null) { + options.alwaysInRange = this.alwaysInRange; + } + this.addOptions(options); + + this.name = name; + + if (this.id == null) { + + this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_"); + + this.div = OpenLayers.Util.createDiv(this.id); + this.div.style.width = "100%"; + this.div.style.height = "100%"; + this.div.dir = "ltr"; + + this.events = new OpenLayers.Events(this, this.div); + if(this.eventListeners instanceof Object) { + this.events.on(this.eventListeners); + } + + } + }, + + /** + * Method: destroy + * Destroy is a destructor: this is to alleviate cyclic references which + * the Javascript garbage cleaner can not take care of on its own. + * + * Parameters: + * setNewBaseLayer - {Boolean} Set a new base layer when this layer has + * been destroyed. Default is true. + */ + destroy: function(setNewBaseLayer) { + if (setNewBaseLayer == null) { + setNewBaseLayer = true; + } + if (this.map != null) { + this.map.removeLayer(this, setNewBaseLayer); + } + this.projection = null; + this.map = null; + this.name = null; + this.div = null; + this.options = null; + + if (this.events) { + if(this.eventListeners) { + this.events.un(this.eventListeners); + } + this.events.destroy(); + } + this.eventListeners = null; + this.events = null; + }, + + /** + * Method: clone + * + * Parameters: + * obj - {} The layer to be cloned + * + * Returns: + * {} An exact clone of this + */ + clone: function (obj) { + + if (obj == null) { + obj = new OpenLayers.Layer(this.name, this.getOptions()); + } + + // catch any randomly tagged-on properties + OpenLayers.Util.applyDefaults(obj, this); + + // a cloned layer should never have its map property set + // because it has not been added to a map yet. + obj.map = null; + + return obj; + }, + + /** + * Method: getOptions + * Extracts an object from the layer with the properties that were set as + * options, but updates them with the values currently set on the + * instance. + * + * Returns: + * {Object} the of the layer, representing the current state. + */ + getOptions: function() { + var options = {}; + for(var o in this.options) { + options[o] = this[o]; + } + return options; + }, + + /** + * APIMethod: setName + * Sets the new layer name for this layer. Can trigger a changelayer event + * on the map. + * + * Parameters: + * newName - {String} The new name. + */ + setName: function(newName) { + if (newName != this.name) { + this.name = newName; + if (this.map != null) { + this.map.events.triggerEvent("changelayer", { + layer: this, + property: "name" + }); + } + } + }, + + /** + * APIMethod: addOptions + * + * Parameters: + * newOptions - {Object} + * reinitialize - {Boolean} If set to true, and if resolution options of the + * current baseLayer were changed, the map will be recentered to make + * sure that it is displayed with a valid resolution, and a + * changebaselayer event will be triggered. + */ + addOptions: function (newOptions, reinitialize) { + + if (this.options == null) { + this.options = {}; + } + + if (newOptions) { + // make sure this.projection references a projection object + if(typeof newOptions.projection == "string") { + newOptions.projection = new OpenLayers.Projection(newOptions.projection); + } + if (newOptions.projection) { + // get maxResolution, units and maxExtent from projection defaults if + // they are not defined already + OpenLayers.Util.applyDefaults(newOptions, + OpenLayers.Projection.defaults[newOptions.projection.getCode()]); + } + // allow array for extents + if (newOptions.maxExtent && !(newOptions.maxExtent instanceof OpenLayers.Bounds)) { + newOptions.maxExtent = new OpenLayers.Bounds(newOptions.maxExtent); + } + if (newOptions.minExtent && !(newOptions.minExtent instanceof OpenLayers.Bounds)) { + newOptions.minExtent = new OpenLayers.Bounds(newOptions.minExtent); + } + } + + // update our copy for clone + OpenLayers.Util.extend(this.options, newOptions); + + // add new options to this + OpenLayers.Util.extend(this, newOptions); + + // get the units from the projection, if we have a projection + // and it it has units + if(this.projection && this.projection.getUnits()) { + this.units = this.projection.getUnits(); + } + + // re-initialize resolutions if necessary, i.e. if any of the + // properties of the "properties" array defined below is set + // in the new options + if(this.map) { + // store current resolution so we can try to restore it later + var resolution = this.map.getResolution(); + var properties = this.RESOLUTION_PROPERTIES.concat( + ["projection", "units", "minExtent", "maxExtent"] + ); + for(var o in newOptions) { + if(newOptions.hasOwnProperty(o) && + OpenLayers.Util.indexOf(properties, o) >= 0) { + + this.initResolutions(); + if (reinitialize && this.map.baseLayer === this) { + // update map position, and restore previous resolution + this.map.setCenter(this.map.getCenter(), + this.map.getZoomForResolution(resolution), + false, true + ); + // trigger a changebaselayer event to make sure that + // all controls (especially + // OpenLayers.Control.PanZoomBar) get notified of the + // new options + this.map.events.triggerEvent("changebaselayer", { + layer: this + }); + } + break; + } + } + } + }, + + /** + * APIMethod: onMapResize + * This function can be implemented by subclasses + */ + onMapResize: function() { + //this function can be implemented by subclasses + }, + + /** + * APIMethod: redraw + * Redraws the layer. Returns true if the layer was redrawn, false if not. + * + * Returns: + * {Boolean} The layer was redrawn. + */ + redraw: function() { + var redrawn = false; + if (this.map) { + + // min/max Range may have changed + this.inRange = this.calculateInRange(); + + // map's center might not yet be set + var extent = this.getExtent(); + + if (extent && this.inRange && this.visibility) { + var zoomChanged = true; + this.moveTo(extent, zoomChanged, false); + this.events.triggerEvent("moveend", + {"zoomChanged": zoomChanged}); + redrawn = true; + } + } + return redrawn; + }, + + /** + * Method: moveTo + * + * Parameters: + * bounds - {} + * zoomChanged - {Boolean} Tells when zoom has changed, as layers have to + * do some init work in that case. + * dragging - {Boolean} + */ + moveTo:function(bounds, zoomChanged, dragging) { + var display = this.visibility; + if (!this.isBaseLayer) { + display = display && this.inRange; + } + this.display(display); + }, + + /** + * Method: moveByPx + * Move the layer based on pixel vector. To be implemented by subclasses. + * + * Parameters: + * dx - {Number} The x coord of the displacement vector. + * dy - {Number} The y coord of the displacement vector. + */ + moveByPx: function(dx, dy) { + }, + + /** + * Method: setMap + * Set the map property for the layer. This is done through an accessor + * so that subclasses can override this and take special action once + * they have their map variable set. + * + * Here we take care to bring over any of the necessary default + * properties from the map. + * + * Parameters: + * map - {} + */ + setMap: function(map) { + if (this.map == null) { + + this.map = map; + + // grab some essential layer data from the map if it hasn't already + // been set + this.maxExtent = this.maxExtent || this.map.maxExtent; + this.minExtent = this.minExtent || this.map.minExtent; + + this.projection = this.projection || this.map.projection; + if (typeof this.projection == "string") { + this.projection = new OpenLayers.Projection(this.projection); + } + + // Check the projection to see if we can get units -- if not, refer + // to properties. + this.units = this.projection.getUnits() || + this.units || this.map.units; + + this.initResolutions(); + + if (!this.isBaseLayer) { + this.inRange = this.calculateInRange(); + var show = ((this.visibility) && (this.inRange)); + this.div.style.display = show ? "" : "none"; + } + + // deal with gutters + this.setTileSize(); + } + }, + + /** + * Method: afterAdd + * Called at the end of the map.addLayer sequence. At this point, the map + * will have a base layer. To be overridden by subclasses. + */ + afterAdd: function() { + }, + + /** + * APIMethod: removeMap + * Just as setMap() allows each layer the possibility to take a + * personalized action on being added to the map, removeMap() allows + * each layer to take a personalized action on being removed from it. + * For now, this will be mostly unused, except for the EventPane layer, + * which needs this hook so that it can remove the special invisible + * pane. + * + * Parameters: + * map - {} + */ + removeMap: function(map) { + //to be overridden by subclasses + }, + + /** + * APIMethod: getImageSize + * + * Parameters: + * bounds - {} optional tile bounds, can be used + * by subclasses that have to deal with different tile sizes at the + * layer extent edges (e.g. Zoomify) + * + * Returns: + * {} The size that the image should be, taking into + * account gutters. + */ + getImageSize: function(bounds) { + return (this.imageSize || this.tileSize); + }, + + /** + * APIMethod: setTileSize + * Set the tile size based on the map size. This also sets layer.imageSize + * or use by Tile.Image. + * + * Parameters: + * size - {} + */ + setTileSize: function(size) { + var tileSize = (size) ? size : + ((this.tileSize) ? this.tileSize : + this.map.getTileSize()); + this.tileSize = tileSize; + if(this.gutter) { + // layers with gutters need non-null tile sizes + //if(tileSize == null) { + // OpenLayers.console.error("Error in layer.setMap() for " + + // this.name + ": layers with " + + // "gutters need non-null tile sizes"); + //} + this.imageSize = new OpenLayers.Size(tileSize.w + (2*this.gutter), + tileSize.h + (2*this.gutter)); + } + }, + + /** + * APIMethod: getVisibility + * + * Returns: + * {Boolean} The layer should be displayed (if in range). + */ + getVisibility: function() { + return this.visibility; + }, + + /** + * APIMethod: setVisibility + * Set the visibility flag for the layer and hide/show & redraw + * accordingly. Fire event unless otherwise specified + * + * Note that visibility is no longer simply whether or not the layer's + * style.display is set to "block". Now we store a 'visibility' state + * property on the layer class, this allows us to remember whether or + * not we *desire* for a layer to be visible. In the case where the + * map's resolution is out of the layer's range, this desire may be + * subverted. + * + * Parameters: + * visibility - {Boolean} Whether or not to display the layer (if in range) + */ + setVisibility: function(visibility) { + if (visibility != this.visibility) { + this.visibility = visibility; + this.display(visibility); + this.redraw(); + if (this.map != null) { + this.map.events.triggerEvent("changelayer", { + layer: this, + property: "visibility" + }); + } + this.events.triggerEvent("visibilitychanged"); + } + }, + + /** + * APIMethod: display + * Hide or show the Layer. This is designed to be used internally, and + * is not generally the way to enable or disable the layer. For that, + * use the setVisibility function instead.. + * + * Parameters: + * display - {Boolean} + */ + display: function(display) { + if (display != (this.div.style.display != "none")) { + this.div.style.display = (display && this.calculateInRange()) ? "block" : "none"; + } + }, + + /** + * APIMethod: calculateInRange + * + * Returns: + * {Boolean} The layer is displayable at the current map's current + * resolution. Note that if 'alwaysInRange' is true for the layer, + * this function will always return true. + */ + calculateInRange: function() { + var inRange = false; + + if (this.alwaysInRange) { + inRange = true; + } else { + if (this.map) { + var resolution = this.map.getResolution(); + inRange = ( (resolution >= this.minResolution) && + (resolution <= this.maxResolution) ); + } + } + return inRange; + }, + + /** + * APIMethod: setIsBaseLayer + * + * Parameters: + * isBaseLayer - {Boolean} + */ + setIsBaseLayer: function(isBaseLayer) { + if (isBaseLayer != this.isBaseLayer) { + this.isBaseLayer = isBaseLayer; + if (this.map != null) { + this.map.events.triggerEvent("changebaselayer", { + layer: this + }); + } + } + }, + + /********************************************************/ + /* */ + /* Baselayer Functions */ + /* */ + /********************************************************/ + + /** + * Method: initResolutions + * This method's responsibility is to set up the 'resolutions' array + * for the layer -- this array is what the layer will use to interface + * between the zoom levels of the map and the resolution display + * of the layer. + * + * The user has several options that determine how the array is set up. + * + * For a detailed explanation, see the following wiki from the + * openlayers.org homepage: + * http://trac.openlayers.org/wiki/SettingZoomLevels + */ + initResolutions: function() { + + // ok we want resolutions, here's our strategy: + // + // 1. if resolutions are defined in the layer config, use them + // 2. else, if scales are defined in the layer config then derive + // resolutions from these scales + // 3. else, attempt to calculate resolutions from maxResolution, + // minResolution, numZoomLevels, maxZoomLevel set in the + // layer config + // 4. if we still don't have resolutions, and if resolutions + // are defined in the same, use them + // 5. else, if scales are defined in the map then derive + // resolutions from these scales + // 6. else, attempt to calculate resolutions from maxResolution, + // minResolution, numZoomLevels, maxZoomLevel set in the + // map + // 7. hope for the best! + + var i, len, p; + var props = {}, alwaysInRange = true; + + // get resolution data from layer config + // (we also set alwaysInRange in the layer as appropriate) + for(i=0, len=this.RESOLUTION_PROPERTIES.length; i} A Bounds object which represents the lon/lat + * bounds of the current viewPort. + */ + getExtent: function() { + // just use stock map calculateBounds function -- passing no arguments + // means it will user map's current center & resolution + // + return this.map.calculateBounds(); + }, + + /** + * APIMethod: getZoomForExtent + * + * Parameters: + * extent - {} + * closest - {Boolean} Find the zoom level that most closely fits the + * specified bounds. Note that this may result in a zoom that does + * not exactly contain the entire extent. + * Default is false. + * + * Returns: + * {Integer} The index of the zoomLevel (entry in the resolutions array) + * for the passed-in extent. We do this by calculating the ideal + * resolution for the given extent (based on the map size) and then + * calling getZoomForResolution(), passing along the 'closest' + * parameter. + */ + getZoomForExtent: function(extent, closest) { + var viewSize = this.map.getSize(); + var idealResolution = Math.max( extent.getWidth() / viewSize.w, + extent.getHeight() / viewSize.h ); + + return this.getZoomForResolution(idealResolution, closest); + }, + + /** + * Method: getDataExtent + * Calculates the max extent which includes all of the data for the layer. + * This function is to be implemented by subclasses. + * + * Returns: + * {} + */ + getDataExtent: function () { + //to be implemented by subclasses + }, + + /** + * APIMethod: getResolutionForZoom + * + * Parameters: + * zoom - {Float} + * + * Returns: + * {Float} A suitable resolution for the specified zoom. + */ + getResolutionForZoom: function(zoom) { + zoom = Math.max(0, Math.min(zoom, this.resolutions.length - 1)); + var resolution; + if(this.map.fractionalZoom) { + var low = Math.floor(zoom); + var high = Math.ceil(zoom); + resolution = this.resolutions[low] - + ((zoom-low) * (this.resolutions[low]-this.resolutions[high])); + } else { + resolution = this.resolutions[Math.round(zoom)]; + } + return resolution; + }, + + /** + * APIMethod: getZoomForResolution + * + * Parameters: + * resolution - {Float} + * closest - {Boolean} Find the zoom level that corresponds to the absolute + * closest resolution, which may result in a zoom whose corresponding + * resolution is actually smaller than we would have desired (if this + * is being called from a getZoomForExtent() call, then this means that + * the returned zoom index might not actually contain the entire + * extent specified... but it'll be close). + * Default is false. + * + * Returns: + * {Integer} The index of the zoomLevel (entry in the resolutions array) + * that corresponds to the best fit resolution given the passed in + * value and the 'closest' specification. + */ + getZoomForResolution: function(resolution, closest) { + var zoom, i, len; + if(this.map.fractionalZoom) { + var lowZoom = 0; + var highZoom = this.resolutions.length - 1; + var highRes = this.resolutions[lowZoom]; + var lowRes = this.resolutions[highZoom]; + var res; + for(i=0, len=this.resolutions.length; i= resolution) { + highRes = res; + lowZoom = i; + } + if(res <= resolution) { + lowRes = res; + highZoom = i; + break; + } + } + var dRes = highRes - lowRes; + if(dRes > 0) { + zoom = lowZoom + ((highRes - resolution) / dRes); + } else { + zoom = lowZoom; + } + } else { + var diff; + var minDiff = Number.POSITIVE_INFINITY; + for(i=0, len=this.resolutions.length; i minDiff) { + break; + } + minDiff = diff; + } else { + if (this.resolutions[i] < resolution) { + break; + } + } + } + zoom = Math.max(0, i-1); + } + return zoom; + }, + + /** + * APIMethod: getLonLatFromViewPortPx + * + * Parameters: + * viewPortPx - {|Object} An OpenLayers.Pixel or + * an object with a 'x' + * and 'y' properties. + * + * Returns: + * {} An OpenLayers.LonLat which is the passed-in + * view port , translated into lon/lat by the layer. + */ + getLonLatFromViewPortPx: function (viewPortPx) { + var lonlat = null; + var map = this.map; + if (viewPortPx != null && map.minPx) { + var res = map.getResolution(); + var maxExtent = map.getMaxExtent({restricted: true}); + var lon = (viewPortPx.x - map.minPx.x) * res + maxExtent.left; + var lat = (map.minPx.y - viewPortPx.y) * res + maxExtent.top; + lonlat = new OpenLayers.LonLat(lon, lat); + + if (this.wrapDateLine) { + lonlat = lonlat.wrapDateLine(this.maxExtent); + } + } + return lonlat; + }, + + /** + * APIMethod: getViewPortPxFromLonLat + * Returns a pixel location given a map location. This method will return + * fractional pixel values. + * + * Parameters: + * lonlat - {|Object} An OpenLayers.LonLat or + * an object with a 'lon' + * and 'lat' properties. + * + * Returns: + * {} An which is the passed-in + * lonlat translated into view port pixels. + */ + getViewPortPxFromLonLat: function (lonlat, resolution) { + var px = null; + if (lonlat != null) { + resolution = resolution || this.map.getResolution(); + var extent = this.map.calculateBounds(null, resolution); + px = new OpenLayers.Pixel( + (1/resolution * (lonlat.lon - extent.left)), + (1/resolution * (extent.top - lonlat.lat)) + ); + } + return px; + }, + + /** + * APIMethod: setOpacity + * Sets the opacity for the entire layer (all images) + * + * Parameters: + * opacity - {Float} + */ + setOpacity: function(opacity) { + if (opacity != this.opacity) { + this.opacity = opacity; + var childNodes = this.div.childNodes; + for(var i = 0, len = childNodes.length; i < len; ++i) { + var element = childNodes[i].firstChild || childNodes[i]; + var lastChild = childNodes[i].lastChild; + //TODO de-uglify this + if (lastChild && lastChild.nodeName.toLowerCase() === "iframe") { + element = lastChild.parentNode; + } + OpenLayers.Util.modifyDOMElement(element, null, null, null, + null, null, null, opacity); + } + if (this.map != null) { + this.map.events.triggerEvent("changelayer", { + layer: this, + property: "opacity" + }); + } + } + }, + + /** + * Method: getZIndex + * + * Returns: + * {Integer} the z-index of this layer + */ + getZIndex: function () { + return this.div.style.zIndex; + }, + + /** + * Method: setZIndex + * + * Parameters: + * zIndex - {Integer} + */ + setZIndex: function (zIndex) { + this.div.style.zIndex = zIndex; + }, + + /** + * Method: adjustBounds + * This function will take a bounds, and if wrapDateLine option is set + * on the layer, it will return a bounds which is wrapped around the + * world. We do not wrap for bounds which *cross* the + * maxExtent.left/right, only bounds which are entirely to the left + * or entirely to the right. + * + * Parameters: + * bounds - {} + */ + adjustBounds: function (bounds) { + + if (this.gutter) { + // Adjust the extent of a bounds in map units by the + // layer's gutter in pixels. + var mapGutter = this.gutter * this.map.getResolution(); + bounds = new OpenLayers.Bounds(bounds.left - mapGutter, + bounds.bottom - mapGutter, + bounds.right + mapGutter, + bounds.top + mapGutter); + } + + if (this.wrapDateLine) { + // wrap around the date line, within the limits of rounding error + var wrappingOptions = { + 'rightTolerance':this.getResolution(), + 'leftTolerance':this.getResolution() + }; + bounds = bounds.wrapDateLine(this.maxExtent, wrappingOptions); + + } + return bounds; + }, + + CLASS_NAME: "OpenLayers.Layer" +}); +/* ====================================================================== + OpenLayers/Layer/HTTPRequest.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Layer.js + */ + +/** + * Class: OpenLayers.Layer.HTTPRequest + * + * Inherits from: + * - + */ +OpenLayers.Layer.HTTPRequest = OpenLayers.Class(OpenLayers.Layer, { + + /** + * Constant: URL_HASH_FACTOR + * {Float} Used to hash URL param strings for multi-WMS server selection. + * Set to the Golden Ratio per Knuth's recommendation. + */ + URL_HASH_FACTOR: (Math.sqrt(5) - 1) / 2, + + /** + * Property: url + * {Array(String) or String} This is either an array of url strings or + * a single url string. + */ + url: null, + + /** + * Property: params + * {Object} Hashtable of key/value parameters + */ + params: null, + + /** + * APIProperty: reproject + * *Deprecated*. See http://docs.openlayers.org/library/spherical_mercator.html + * for information on the replacement for this functionality. + * {Boolean} Whether layer should reproject itself based on base layer + * locations. This allows reprojection onto commercial layers. + * Default is false: Most layers can't reproject, but layers + * which can create non-square geographic pixels can, like WMS. + * + */ + reproject: false, + + /** + * Constructor: OpenLayers.Layer.HTTPRequest + * + * Parameters: + * name - {String} + * url - {Array(String) or String} + * params - {Object} + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function(name, url, params, options) { + OpenLayers.Layer.prototype.initialize.apply(this, [name, options]); + this.url = url; + if (!this.params) { + this.params = OpenLayers.Util.extend({}, params); + } + }, + + /** + * APIMethod: destroy + */ + destroy: function() { + this.url = null; + this.params = null; + OpenLayers.Layer.prototype.destroy.apply(this, arguments); + }, + + /** + * APIMethod: clone + * + * Parameters: + * obj - {Object} + * + * Returns: + * {} An exact clone of this + * + */ + clone: function (obj) { + + if (obj == null) { + obj = new OpenLayers.Layer.HTTPRequest(this.name, + this.url, + this.params, + this.getOptions()); + } + + //get all additions from superclasses + obj = OpenLayers.Layer.prototype.clone.apply(this, [obj]); + + // copy/set any non-init, non-simple values here + + return obj; + }, + + /** + * APIMethod: setUrl + * + * Parameters: + * newUrl - {String} + */ + setUrl: function(newUrl) { + this.url = newUrl; + }, + + /** + * APIMethod: mergeNewParams + * + * Parameters: + * newParams - {Object} + * + * Returns: + * redrawn: {Boolean} whether the layer was actually redrawn. + */ + mergeNewParams:function(newParams) { + this.params = OpenLayers.Util.extend(this.params, newParams); + var ret = this.redraw(); + if(this.map != null) { + this.map.events.triggerEvent("changelayer", { + layer: this, + property: "params" + }); + } + return ret; + }, + + /** + * APIMethod: redraw + * Redraws the layer. Returns true if the layer was redrawn, false if not. + * + * Parameters: + * force - {Boolean} Force redraw by adding random parameter. + * + * Returns: + * {Boolean} The layer was redrawn. + */ + redraw: function(force) { + if (force) { + return this.mergeNewParams({"_olSalt": Math.random()}); + } else { + return OpenLayers.Layer.prototype.redraw.apply(this, []); + } + }, + + /** + * Method: selectUrl + * selectUrl() implements the standard floating-point multiplicative + * hash function described by Knuth, and hashes the contents of the + * given param string into a float between 0 and 1. This float is then + * scaled to the size of the provided urls array, and used to select + * a URL. + * + * Parameters: + * paramString - {String} + * urls - {Array(String)} + * + * Returns: + * {String} An entry from the urls array, deterministically selected based + * on the paramString. + */ + selectUrl: function(paramString, urls) { + var product = 1; + for (var i=0, len=paramString.length; i constructor, or a subclass. + * + * TBD 3.0 - remove reference to url in above paragraph + * + */ +OpenLayers.Tile = OpenLayers.Class({ + + /** + * APIProperty: events + * {} An events object that handles all + * events on the tile. + * + * Register a listener for a particular event with the following syntax: + * (code) + * tile.events.register(type, obj, listener); + * (end) + * + * Supported event types: + * beforedraw - Triggered before the tile is drawn. Used to defer + * drawing to an animation queue. To defer drawing, listeners need + * to return false, which will abort drawing. The queue handler needs + * to call (true) to actually draw the tile. + * loadstart - Triggered when tile loading starts. + * loadend - Triggered when tile loading ends. + * loaderror - Triggered before the loadend event (i.e. when the tile is + * still hidden) if the tile could not be loaded. + * reload - Triggered when an already loading tile is reloaded. + * unload - Triggered before a tile is unloaded. + */ + events: null, + + /** + * APIProperty: eventListeners + * {Object} If set as an option at construction, the eventListeners + * object will be registered with . Object + * structure must be a listeners object as shown in the example for + * the events.on method. + * + * This options can be set in the ``tileOptions`` option from + * . For example, to be notified of the + * ``loadend`` event of each tiles: + * (code) + * new OpenLayers.Layer.OSM('osm', 'http://tile.openstreetmap.org/${z}/${x}/${y}.png', { + * tileOptions: { + * eventListeners: { + * 'loadend': function(evt) { + * // do something on loadend + * } + * } + * } + * }); + * (end) + */ + eventListeners: null, + + /** + * Property: id + * {String} null + */ + id: null, + + /** + * Property: layer + * {} layer the tile is attached to + */ + layer: null, + + /** + * Property: url + * {String} url of the request. + * + * TBD 3.0 + * Deprecated. The base tile class does not need an url. This should be + * handled in subclasses. Does not belong here. + */ + url: null, + + /** + * APIProperty: bounds + * {} null + */ + bounds: null, + + /** + * Property: size + * {} null + */ + size: null, + + /** + * Property: position + * {} Top Left pixel of the tile + */ + position: null, + + /** + * Property: isLoading + * {Boolean} Is the tile loading? + */ + isLoading: false, + + /** TBD 3.0 -- remove 'url' from the list of parameters to the constructor. + * there is no need for the base tile class to have a url. + */ + + /** + * Constructor: OpenLayers.Tile + * Constructor for a new instance. + * + * Parameters: + * layer - {} layer that the tile will go in. + * position - {} + * bounds - {} + * url - {} + * size - {} + * options - {Object} + */ + initialize: function(layer, position, bounds, url, size, options) { + this.layer = layer; + this.position = position.clone(); + this.setBounds(bounds); + this.url = url; + if (size) { + this.size = size.clone(); + } + + //give the tile a unique id based on its BBOX. + this.id = OpenLayers.Util.createUniqueID("Tile_"); + + OpenLayers.Util.extend(this, options); + + this.events = new OpenLayers.Events(this); + if (this.eventListeners instanceof Object) { + this.events.on(this.eventListeners); + } + }, + + /** + * Method: unload + * Call immediately before destroying if you are listening to tile + * events, so that counters are properly handled if tile is still + * loading at destroy-time. Will only fire an event if the tile is + * still loading. + */ + unload: function() { + if (this.isLoading) { + this.isLoading = false; + this.events.triggerEvent("unload"); + } + }, + + /** + * APIMethod: destroy + * Nullify references to prevent circular references and memory leaks. + */ + destroy:function() { + this.layer = null; + this.bounds = null; + this.size = null; + this.position = null; + + if (this.eventListeners) { + this.events.un(this.eventListeners); + } + this.events.destroy(); + this.eventListeners = null; + this.events = null; + }, + + /** + * Method: draw + * Clear whatever is currently in the tile, then return whether or not + * it should actually be re-drawn. This is an example implementation + * that can be overridden by subclasses. The minimum thing to do here + * is to call and return the result from . + * + * Parameters: + * force - {Boolean} If true, the tile will not be cleared and no beforedraw + * event will be fired. This is used for drawing tiles asynchronously + * after drawing has been cancelled by returning false from a beforedraw + * listener. + * + * Returns: + * {Boolean} Whether or not the tile should actually be drawn. Returns null + * if a beforedraw listener returned false. + */ + draw: function(force) { + if (!force) { + //clear tile's contents and mark as not drawn + this.clear(); + } + var draw = this.shouldDraw(); + if (draw && !force && this.events.triggerEvent("beforedraw") === false) { + draw = null; + } + return draw; + }, + + /** + * Method: shouldDraw + * Return whether or not the tile should actually be (re-)drawn. The only + * case where we *wouldn't* want to draw the tile is if the tile is outside + * its layer's maxExtent + * + * Returns: + * {Boolean} Whether or not the tile should actually be drawn. + */ + shouldDraw: function() { + var withinMaxExtent = false, + maxExtent = this.layer.maxExtent; + if (maxExtent) { + var map = this.layer.map; + var worldBounds = map.baseLayer.wrapDateLine && map.getMaxExtent(); + if (this.bounds.intersectsBounds(maxExtent, {inclusive: false, worldBounds: worldBounds})) { + withinMaxExtent = true; + } + } + + return withinMaxExtent || this.layer.displayOutsideMaxExtent; + }, + + /** + * Method: setBounds + * Sets the bounds on this instance + * + * Parameters: + * bounds {} + */ + setBounds: function(bounds) { + bounds = bounds.clone(); + if (this.layer.map.baseLayer.wrapDateLine) { + var worldExtent = this.layer.map.getMaxExtent(), + tolerance = this.layer.map.getResolution(); + bounds = bounds.wrapDateLine(worldExtent, { + leftTolerance: tolerance, + rightTolerance: tolerance + }); + } + this.bounds = bounds; + }, + + /** + * Method: moveTo + * Reposition the tile. + * + * Parameters: + * bounds - {} + * position - {} + * redraw - {Boolean} Call draw method on tile after moving. + * Default is true + */ + moveTo: function (bounds, position, redraw) { + if (redraw == null) { + redraw = true; + } + + this.setBounds(bounds); + this.position = position.clone(); + if (redraw) { + this.draw(); + } + }, + + /** + * Method: clear + * Clear the tile of any bounds/position-related data so that it can + * be reused in a new location. + */ + clear: function(draw) { + // to be extended by subclasses + }, + + CLASS_NAME: "OpenLayers.Tile" +}); +/* ====================================================================== + OpenLayers/Tile/Image.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Tile.js + * @requires OpenLayers/Animation.js + * @requires OpenLayers/Util.js + */ + +/** + * Class: OpenLayers.Tile.Image + * Instances of OpenLayers.Tile.Image are used to manage the image tiles + * used by various layers. Create a new image tile with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, { + + /** + * APIProperty: events + * {} An events object that handles all + * events on the tile. + * + * Register a listener for a particular event with the following syntax: + * (code) + * tile.events.register(type, obj, listener); + * (end) + * + * Supported event types (in addition to the events): + * beforeload - Triggered before an image is prepared for loading, when the + * url for the image is known already. Listeners may call on + * the tile instance. If they do so, that image will be used and no new + * one will be created. + */ + + /** + * APIProperty: url + * {String} The URL of the image being requested. No default. Filled in by + * layer.getURL() function. May be modified by loadstart listeners. + */ + url: null, + + /** + * Property: imgDiv + * {HTMLImageElement} The image for this tile. + */ + imgDiv: null, + + /** + * Property: frame + * {DOMElement} The image element is appended to the frame. Any gutter on + * the image will be hidden behind the frame. If no gutter is set, + * this will be null. + */ + frame: null, + + /** + * Property: imageReloadAttempts + * {Integer} Attempts to load the image. + */ + imageReloadAttempts: null, + + /** + * Property: layerAlphaHack + * {Boolean} True if the png alpha hack needs to be applied on the layer's div. + */ + layerAlphaHack: null, + + /** + * Property: asyncRequestId + * {Integer} ID of an request to see if request is still valid. This is a + * number which increments by 1 for each asynchronous request. + */ + asyncRequestId: null, + + /** + * APIProperty: maxGetUrlLength + * {Number} If set, requests that would result in GET urls with more + * characters than the number provided will be made using form-encoded + * HTTP POST. It is good practice to avoid urls that are longer than 2048 + * characters. + * + * Caution: + * Older versions of Gecko based browsers (e.g. Firefox < 3.5) and most + * Opera versions do not fully support this option. On all browsers, + * transition effects are not supported if POST requests are used. + */ + maxGetUrlLength: null, + + /** + * Property: canvasContext + * {CanvasRenderingContext2D} A canvas context associated with + * the tile image. + */ + canvasContext: null, + + /** + * APIProperty: crossOriginKeyword + * The value of the crossorigin keyword to use when loading images. This is + * only relevant when using for tiles from remote + * origins and should be set to either 'anonymous' or 'use-credentials' + * for servers that send Access-Control-Allow-Origin headers with their + * tiles. + */ + crossOriginKeyword: null, + + /** TBD 3.0 - reorder the parameters to the init function to remove + * URL. the getUrl() function on the layer gets called on + * each draw(), so no need to specify it here. + */ + + /** + * Constructor: OpenLayers.Tile.Image + * Constructor for a new instance. + * + * Parameters: + * layer - {} layer that the tile will go in. + * position - {} + * bounds - {} + * url - {} Deprecated. Remove me in 3.0. + * size - {} + * options - {Object} + */ + initialize: function(layer, position, bounds, url, size, options) { + OpenLayers.Tile.prototype.initialize.apply(this, arguments); + + this.url = url; //deprecated remove me + + this.layerAlphaHack = this.layer.alpha && OpenLayers.Util.alphaHack(); + + if (this.maxGetUrlLength != null || this.layer.gutter || this.layerAlphaHack) { + // only create frame if it's needed + this.frame = document.createElement("div"); + this.frame.style.position = "absolute"; + this.frame.style.overflow = "hidden"; + } + if (this.maxGetUrlLength != null) { + OpenLayers.Util.extend(this, OpenLayers.Tile.Image.IFrame); + } + }, + + /** + * APIMethod: destroy + * nullify references to prevent circular references and memory leaks + */ + destroy: function() { + if (this.imgDiv) { + this.clear(); + this.imgDiv = null; + this.frame = null; + } + // don't handle async requests any more + this.asyncRequestId = null; + OpenLayers.Tile.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: draw + * Check that a tile should be drawn, and draw it. + * + * Returns: + * {Boolean} Was a tile drawn? Or null if a beforedraw listener returned + * false. + */ + draw: function() { + var shouldDraw = OpenLayers.Tile.prototype.draw.apply(this, arguments); + if (shouldDraw) { + // The layer's reproject option is deprecated. + if (this.layer != this.layer.map.baseLayer && this.layer.reproject) { + // getBoundsFromBaseLayer is defined in deprecated.js. + this.bounds = this.getBoundsFromBaseLayer(this.position); + } + if (this.isLoading) { + //if we're already loading, send 'reload' instead of 'loadstart'. + this._loadEvent = "reload"; + } else { + this.isLoading = true; + this._loadEvent = "loadstart"; + } + this.renderTile(); + this.positionTile(); + } else if (shouldDraw === false) { + this.unload(); + } + return shouldDraw; + }, + + /** + * Method: renderTile + * Internal function to actually initialize the image tile, + * position it correctly, and set its url. + */ + renderTile: function() { + if (this.layer.async) { + // Asynchronous image requests call the asynchronous getURL method + // on the layer to fetch an image that covers 'this.bounds'. + var id = this.asyncRequestId = (this.asyncRequestId || 0) + 1; + this.layer.getURLasync(this.bounds, function(url) { + if (id == this.asyncRequestId) { + this.url = url; + this.initImage(); + } + }, this); + } else { + // synchronous image requests get the url immediately. + this.url = this.layer.getURL(this.bounds); + this.initImage(); + } + }, + + /** + * Method: positionTile + * Using the properties currenty set on the layer, position the tile correctly. + * This method is used both by the async and non-async versions of the Tile.Image + * code. + */ + positionTile: function() { + var style = this.getTile().style, + size = this.frame ? this.size : + this.layer.getImageSize(this.bounds), + ratio = 1; + if (this.layer instanceof OpenLayers.Layer.Grid) { + ratio = this.layer.getServerResolution() / this.layer.map.getResolution(); + } + style.left = this.position.x + "px"; + style.top = this.position.y + "px"; + style.width = Math.round(ratio * size.w) + "px"; + style.height = Math.round(ratio * size.h) + "px"; + }, + + /** + * Method: clear + * Remove the tile from the DOM, clear it of any image related data so that + * it can be reused in a new location. + */ + clear: function() { + OpenLayers.Tile.prototype.clear.apply(this, arguments); + var img = this.imgDiv; + if (img) { + var tile = this.getTile(); + if (tile.parentNode === this.layer.div) { + this.layer.div.removeChild(tile); + } + this.setImgSrc(); + if (this.layerAlphaHack === true) { + img.style.filter = ""; + } + OpenLayers.Element.removeClass(img, "olImageLoadError"); + } + this.canvasContext = null; + }, + + /** + * Method: getImage + * Returns or creates and returns the tile image. + */ + getImage: function() { + if (!this.imgDiv) { + this.imgDiv = OpenLayers.Tile.Image.IMAGE.cloneNode(false); + + var style = this.imgDiv.style; + if (this.frame) { + var left = 0, top = 0; + if (this.layer.gutter) { + left = this.layer.gutter / this.layer.tileSize.w * 100; + top = this.layer.gutter / this.layer.tileSize.h * 100; + } + style.left = -left + "%"; + style.top = -top + "%"; + style.width = (2 * left + 100) + "%"; + style.height = (2 * top + 100) + "%"; + } + style.visibility = "hidden"; + style.opacity = 0; + if (this.layer.opacity < 1) { + style.filter = 'alpha(opacity=' + + (this.layer.opacity * 100) + + ')'; + } + style.position = "absolute"; + if (this.layerAlphaHack) { + // move the image out of sight + style.paddingTop = style.height; + style.height = "0"; + style.width = "100%"; + } + if (this.frame) { + this.frame.appendChild(this.imgDiv); + } + } + + return this.imgDiv; + }, + + /** + * APIMethod: setImage + * Sets the image element for this tile. This method should only be called + * from beforeload listeners. + * + * Parameters + * img - {HTMLImageElement} The image to use for this tile. + */ + setImage: function(img) { + this.imgDiv = img; + }, + + /** + * Method: initImage + * Creates the content for the frame on the tile. + */ + initImage: function() { + if (!this.url && !this.imgDiv) { + // fast path out - if there is no tile url and no previous image + this.isLoading = false; + return; + } + this.events.triggerEvent('beforeload'); + this.layer.div.appendChild(this.getTile()); + this.events.triggerEvent(this._loadEvent); + var img = this.getImage(); + var src = img.getAttribute('src') || ''; + if (this.url && OpenLayers.Util.isEquivalentUrl(src, this.url)) { + this._loadTimeout = window.setTimeout( + OpenLayers.Function.bind(this.onImageLoad, this), 0 + ); + } else { + this.stopLoading(); + if (this.crossOriginKeyword) { + img.removeAttribute("crossorigin"); + } + OpenLayers.Event.observe(img, "load", + OpenLayers.Function.bind(this.onImageLoad, this) + ); + OpenLayers.Event.observe(img, "error", + OpenLayers.Function.bind(this.onImageError, this) + ); + this.imageReloadAttempts = 0; + this.setImgSrc(this.url); + } + }, + + /** + * Method: setImgSrc + * Sets the source for the tile image + * + * Parameters: + * url - {String} or undefined to hide the image + */ + setImgSrc: function(url) { + var img = this.imgDiv; + if (url) { + img.style.visibility = 'hidden'; + img.style.opacity = 0; + // don't set crossOrigin if the url is a data URL + if (this.crossOriginKeyword) { + if (url.substr(0, 5) !== 'data:') { + img.setAttribute("crossorigin", this.crossOriginKeyword); + } else { + img.removeAttribute("crossorigin"); + } + } + img.src = url; + } else { + // Remove reference to the image, and leave it to the browser's + // caching and garbage collection. + this.stopLoading(); + this.imgDiv = null; + if (img.parentNode) { + img.parentNode.removeChild(img); + } + } + }, + + /** + * Method: getTile + * Get the tile's markup. + * + * Returns: + * {DOMElement} The tile's markup + */ + getTile: function() { + return this.frame ? this.frame : this.getImage(); + }, + + /** + * Method: createBackBuffer + * Create a backbuffer for this tile. A backbuffer isn't exactly a clone + * of the tile's markup, because we want to avoid the reloading of the + * image. So we clone the frame, and steal the image from the tile. + * + * Returns: + * {DOMElement} The markup, or undefined if the tile has no image + * or if it's currently loading. + */ + createBackBuffer: function() { + if (!this.imgDiv || this.isLoading) { + return; + } + var backBuffer; + if (this.frame) { + backBuffer = this.frame.cloneNode(false); + backBuffer.appendChild(this.imgDiv); + } else { + backBuffer = this.imgDiv; + } + this.imgDiv = null; + return backBuffer; + }, + + /** + * Method: onImageLoad + * Handler for the image onload event + */ + onImageLoad: function() { + var img = this.imgDiv; + this.stopLoading(); + img.style.visibility = 'inherit'; + img.style.opacity = this.layer.opacity; + this.isLoading = false; + this.canvasContext = null; + this.events.triggerEvent("loadend"); + + if (this.layerAlphaHack === true) { + img.style.filter = + "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + + img.src + "', sizingMethod='scale')"; + } + }, + + /** + * Method: onImageError + * Handler for the image onerror event + */ + onImageError: function() { + var img = this.imgDiv; + if (img.src != null) { + this.imageReloadAttempts++; + if (this.imageReloadAttempts <= OpenLayers.IMAGE_RELOAD_ATTEMPTS) { + this.setImgSrc(this.layer.getURL(this.bounds)); + } else { + OpenLayers.Element.addClass(img, "olImageLoadError"); + this.events.triggerEvent("loaderror"); + this.onImageLoad(); + } + } + }, + + /** + * Method: stopLoading + * Stops a loading sequence so won't be executed. + */ + stopLoading: function() { + OpenLayers.Event.stopObservingElement(this.imgDiv); + window.clearTimeout(this._loadTimeout); + delete this._loadTimeout; + }, + + /** + * APIMethod: getCanvasContext + * Returns a canvas context associated with the tile image (with + * the image drawn on it). + * Returns undefined if the browser does not support canvas, if + * the tile has no image or if it's currently loading. + * + * The function returns a canvas context instance but the + * underlying canvas is still available in the 'canvas' property: + * (code) + * var context = tile.getCanvasContext(); + * if (context) { + * var data = context.canvas.toDataURL('image/jpeg'); + * } + * (end) + * + * Returns: + * {Boolean} + */ + getCanvasContext: function() { + if (OpenLayers.CANVAS_SUPPORTED && this.imgDiv && !this.isLoading) { + if (!this.canvasContext) { + var canvas = document.createElement("canvas"); + canvas.width = this.size.w; + canvas.height = this.size.h; + this.canvasContext = canvas.getContext("2d"); + this.canvasContext.drawImage(this.imgDiv, 0, 0); + } + return this.canvasContext; + } + }, + + CLASS_NAME: "OpenLayers.Tile.Image" + +}); + +/** + * Constant: OpenLayers.Tile.Image.IMAGE + * {HTMLImageElement} The image for a tile. + */ +OpenLayers.Tile.Image.IMAGE = (function() { + var img = new Image(); + img.className = "olTileImage"; + // avoid image gallery menu in IE6 + img.galleryImg = "no"; + return img; +}()); + +/* ====================================================================== + OpenLayers/Layer/Grid.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Layer/HTTPRequest.js + * @requires OpenLayers/Tile/Image.js + */ + +/** + * Class: OpenLayers.Layer.Grid + * Base class for layers that use a lattice of tiles. Create a new grid + * layer with the constructor. + * + * Inherits from: + * - + */ +OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { + + /** + * APIProperty: tileSize + * {} + */ + tileSize: null, + + /** + * Property: tileOriginCorner + * {String} If the property is not provided, the tile origin + * will be derived from the layer's . The corner of the + * used is determined by this property. Acceptable values + * are "tl" (top left), "tr" (top right), "bl" (bottom left), and "br" + * (bottom right). Default is "bl". + */ + tileOriginCorner: "bl", + + /** + * APIProperty: tileOrigin + * {} Optional origin for aligning the grid of tiles. + * If provided, requests for tiles at all resolutions will be aligned + * with this location (no tiles shall overlap this location). If + * not provided, the grid of tiles will be aligned with the layer's + * . Default is ``null``. + */ + tileOrigin: null, + + /** APIProperty: tileOptions + * {Object} optional configuration options for instances + * created by this Layer, if supported by the tile class. + */ + tileOptions: null, + + /** + * APIProperty: tileClass + * {} The tile class to use for this layer. + * Defaults is OpenLayers.Tile.Image. + */ + tileClass: OpenLayers.Tile.Image, + + /** + * Property: grid + * {Array(Array())} This is an array of rows, each row is + * an array of tiles. + */ + grid: null, + + /** + * APIProperty: singleTile + * {Boolean} Moves the layer into single-tile mode, meaning that one tile + * will be loaded. The tile's size will be determined by the 'ratio' + * property. When the tile is dragged such that it does not cover the + * entire viewport, it is reloaded. + */ + singleTile: false, + + /** APIProperty: ratio + * {Float} Used only when in single-tile mode, this specifies the + * ratio of the size of the single tile to the size of the map. + * Default value is 1.5. + */ + ratio: 1.5, + + /** + * APIProperty: buffer + * {Integer} Used only when in gridded mode, this specifies the number of + * extra rows and colums of tiles on each side which will + * surround the minimum grid tiles to cover the map. + * For very slow loading layers, a larger value may increase + * performance somewhat when dragging, but will increase bandwidth + * use significantly. + */ + buffer: 0, + + /** + * APIProperty: transitionEffect + * {String} The transition effect to use when the map is zoomed. + * Two posible values: + * + * "resize" - Existing tiles are resized on zoom to provide a visual + * effect of the zoom having taken place immediately. As the + * new tiles become available, they are drawn on top of the + * resized tiles (this is the default setting). + * "map-resize" - Existing tiles are resized on zoom and placed below the + * base layer. New tiles for the base layer will cover existing tiles. + * This setting is recommended when having an overlay duplicated during + * the transition is undesirable (e.g. street labels or big transparent + * fills). + * null - No transition effect. + * + * Using "resize" on non-opaque layers can cause undesired visual + * effects. Set transitionEffect to null in this case. + */ + transitionEffect: "resize", + + /** + * APIProperty: numLoadingTiles + * {Integer} How many tiles are still loading? + */ + numLoadingTiles: 0, + + /** + * Property: serverResolutions + * {Array(Number}} This property is documented in subclasses as + * an API property. + */ + serverResolutions: null, + + /** + * Property: loading + * {Boolean} Indicates if tiles are being loaded. + */ + loading: false, + + /** + * Property: backBuffer + * {DOMElement} The back buffer. + */ + backBuffer: null, + + /** + * Property: gridResolution + * {Number} The resolution of the current grid. Used for backbuffer and + * client zoom. This property is updated every time the grid is + * initialized. + */ + gridResolution: null, + + /** + * Property: backBufferResolution + * {Number} The resolution of the current back buffer. This property is + * updated each time a back buffer is created. + */ + backBufferResolution: null, + + /** + * Property: backBufferLonLat + * {Object} The top-left corner of the current back buffer. Includes lon + * and lat properties. This object is updated each time a back buffer + * is created. + */ + backBufferLonLat: null, + + /** + * Property: backBufferTimerId + * {Number} The id of the back buffer timer. This timer is used to + * delay the removal of the back buffer, thereby preventing + * flash effects caused by tile animation. + */ + backBufferTimerId: null, + + /** + * APIProperty: removeBackBufferDelay + * {Number} Delay for removing the backbuffer when all tiles have finished + * loading. Can be set to 0 when no css opacity transitions for the + * olTileImage class are used. Default is 0 for layers, + * 2500 for tiled layers. See for more information on + * tile animation. + */ + removeBackBufferDelay: null, + + /** + * APIProperty: className + * {String} Name of the class added to the layer div. If not set in the + * options passed to the constructor then className defaults to + * "olLayerGridSingleTile" for single tile layers (see ), + * and "olLayerGrid" for non single tile layers. + * + * Note: + * + * The displaying of tiles is not animated by default for single tile + * layers - OpenLayers' default theme (style.css) includes this: + * (code) + * .olLayerGrid .olTileImage { + * -webkit-transition: opacity 0.2s linear; + * -moz-transition: opacity 0.2s linear; + * -o-transition: opacity 0.2s linear; + * transition: opacity 0.2s linear; + * } + * (end) + * To animate tile displaying for any grid layer the following + * CSS rule can be used: + * (code) + * .olTileImage { + * -webkit-transition: opacity 0.2s linear; + * -moz-transition: opacity 0.2s linear; + * -o-transition: opacity 0.2s linear; + * transition: opacity 0.2s linear; + * } + * (end) + * In that case, to avoid flash effects, + * should not be zero. + */ + className: null, + + /** + * Register a listener for a particular event with the following syntax: + * (code) + * layer.events.register(type, obj, listener); + * (end) + * + * Listeners will be called with a reference to an event object. The + * properties of this event depends on exactly what happened. + * + * All event objects have at least the following properties: + * object - {Object} A reference to layer.events.object. + * element - {DOMElement} A reference to layer.events.element. + * + * Supported event types: + * addtile - Triggered when a tile is added to this layer. Listeners receive + * an object as first argument, which has a tile property that + * references the tile that has been added. + * tileloadstart - Triggered when a tile starts loading. Listeners receive + * an object as first argument, which has a tile property that + * references the tile that starts loading. + * tileloaded - Triggered when each new tile is + * loaded, as a means of progress update to listeners. + * listeners can access 'numLoadingTiles' if they wish to keep + * track of the loading progress. Listeners are called with an object + * with a 'tile' property as first argument, making the loaded tile + * available to the listener, and an 'aborted' property, which will be + * true when loading was aborted and no tile data is available. + * tileerror - Triggered before the tileloaded event (i.e. when the tile is + * still hidden) if a tile failed to load. Listeners receive an object + * as first argument, which has a tile property that references the + * tile that could not be loaded. + * retile - Triggered when the layer recreates its tile grid. + */ + + /** + * Property: gridLayout + * {Object} Object containing properties tilelon, tilelat, startcol, + * startrow + */ + gridLayout: null, + + /** + * Property: rowSign + * {Number} 1 for grids starting at the top, -1 for grids starting at the + * bottom. This is used for several grid index and offset calculations. + */ + rowSign: null, + + /** + * Property: transitionendEvents + * {Array} Event names for transitionend + */ + transitionendEvents: [ + 'transitionend', 'webkitTransitionEnd', 'otransitionend', + 'oTransitionEnd' + ], + + /** + * Constructor: OpenLayers.Layer.Grid + * Create a new grid layer + * + * Parameters: + * name - {String} + * url - {String} + * params - {Object} + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function(name, url, params, options) { + OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this, + arguments); + this.grid = []; + this._removeBackBuffer = OpenLayers.Function.bind(this.removeBackBuffer, this); + + this.initProperties(); + + this.rowSign = this.tileOriginCorner.substr(0, 1) === "t" ? 1 : -1; + }, + + /** + * Method: initProperties + * Set any properties that depend on the value of singleTile. + * Currently sets removeBackBufferDelay and className + */ + initProperties: function() { + if (this.options.removeBackBufferDelay === undefined) { + this.removeBackBufferDelay = this.singleTile ? 0 : 2500; + } + + if (this.options.className === undefined) { + this.className = this.singleTile ? 'olLayerGridSingleTile' : + 'olLayerGrid'; + } + }, + + /** + * Method: setMap + * + * Parameters: + * map - {} The map. + */ + setMap: function(map) { + OpenLayers.Layer.HTTPRequest.prototype.setMap.call(this, map); + OpenLayers.Element.addClass(this.div, this.className); + }, + + /** + * Method: removeMap + * Called when the layer is removed from the map. + * + * Parameters: + * map - {} The map. + */ + removeMap: function(map) { + this.removeBackBuffer(); + }, + + /** + * APIMethod: destroy + * Deconstruct the layer and clear the grid. + */ + destroy: function() { + this.removeBackBuffer(); + this.clearGrid(); + + this.grid = null; + this.tileSize = null; + OpenLayers.Layer.HTTPRequest.prototype.destroy.apply(this, arguments); + }, + + /** + * APIMethod: mergeNewParams + * Refetches tiles with new params merged, keeping a backbuffer. Each + * loading new tile will have a css class of '.olTileReplacing'. If a + * stylesheet applies a 'display: none' style to that class, any fade-in + * transition will not apply, and backbuffers for each tile will be removed + * as soon as the tile is loaded. + * + * Parameters: + * newParams - {Object} + * + * Returns: + * redrawn: {Boolean} whether the layer was actually redrawn. + */ + + /** + * Method: clearGrid + * Go through and remove all tiles from the grid, calling + * destroy() on each of them to kill circular references + */ + clearGrid:function() { + if (this.grid) { + for(var iRow=0, len=this.grid.length; iRow} An exact clone of this OpenLayers.Layer.Grid + */ + clone: function (obj) { + + if (obj == null) { + obj = new OpenLayers.Layer.Grid(this.name, + this.url, + this.params, + this.getOptions()); + } + + //get all additions from superclasses + obj = OpenLayers.Layer.HTTPRequest.prototype.clone.apply(this, [obj]); + + // copy/set any non-init, non-simple values here + if (this.tileSize != null) { + obj.tileSize = this.tileSize.clone(); + } + + // we do not want to copy reference to grid, so we make a new array + obj.grid = []; + obj.gridResolution = null; + // same for backbuffer + obj.backBuffer = null; + obj.backBufferTimerId = null; + obj.loading = false; + obj.numLoadingTiles = 0; + + return obj; + }, + + /** + * Method: moveTo + * This function is called whenever the map is moved. All the moving + * of actual 'tiles' is done by the map, but moveTo's role is to accept + * a bounds and make sure the data that that bounds requires is pre-loaded. + * + * Parameters: + * bounds - {} + * zoomChanged - {Boolean} + * dragging - {Boolean} + */ + moveTo:function(bounds, zoomChanged, dragging) { + + OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this, arguments); + + bounds = bounds || this.map.getExtent(); + + if (bounds != null) { + + // if grid is empty or zoom has changed, we *must* re-tile + var forceReTile = !this.grid.length || zoomChanged; + + // total bounds of the tiles + var tilesBounds = this.getTilesBounds(); + + // the new map resolution + var resolution = this.map.getResolution(); + + // the server-supported resolution for the new map resolution + var serverResolution = this.getServerResolution(resolution); + + if (this.singleTile) { + + // We want to redraw whenever even the slightest part of the + // current bounds is not contained by our tile. + // (thus, we do not specify partial -- its default is false) + + if ( forceReTile || + (!dragging && !tilesBounds.containsBounds(bounds))) { + + // In single tile mode with no transition effect, we insert + // a non-scaled backbuffer when the layer is moved. But if + // a zoom occurs right after a move, i.e. before the new + // image is received, we need to remove the backbuffer, or + // an ill-positioned image will be visible during the zoom + // transition. + + if(zoomChanged && this.transitionEffect !== 'resize') { + this.removeBackBuffer(); + } + + if(!zoomChanged || this.transitionEffect === 'resize') { + this.applyBackBuffer(resolution); + } + + this.initSingleTile(bounds); + } + } else { + + // if the bounds have changed such that they are not even + // *partially* contained by our tiles (e.g. when user has + // programmatically panned to the other side of the earth on + // zoom level 18), then moveGriddedTiles could potentially have + // to run through thousands of cycles, so we want to reTile + // instead (thus, partial true). + forceReTile = forceReTile || + !tilesBounds.intersectsBounds(bounds, { + worldBounds: this.map.baseLayer.wrapDateLine && + this.map.getMaxExtent() + }); + + if(forceReTile) { + if(zoomChanged && (this.transitionEffect === 'resize' || + this.gridResolution === resolution)) { + this.applyBackBuffer(resolution); + } + this.initGriddedTiles(bounds); + } else { + this.moveGriddedTiles(); + } + } + } + }, + + /** + * Method: getTileData + * Given a map location, retrieve a tile and the pixel offset within that + * tile corresponding to the location. If there is not an existing + * tile in the grid that covers the given location, null will be + * returned. + * + * Parameters: + * loc - {} map location + * + * Returns: + * {Object} Object with the following properties: tile ({}), + * i ({Number} x-pixel offset from top left), and j ({Integer} y-pixel + * offset from top left). + */ + getTileData: function(loc) { + var data = null, + x = loc.lon, + y = loc.lat, + numRows = this.grid.length; + + if (this.map && numRows) { + var res = this.map.getResolution(), + tileWidth = this.tileSize.w, + tileHeight = this.tileSize.h, + bounds = this.grid[0][0].bounds, + left = bounds.left, + top = bounds.top; + + if (x < left) { + // deal with multiple worlds + if (this.map.baseLayer.wrapDateLine) { + var worldWidth = this.map.getMaxExtent().getWidth(); + var worldsAway = Math.ceil((left - x) / worldWidth); + x += worldWidth * worldsAway; + } + } + // tile distance to location (fractional number of tiles); + var dtx = (x - left) / (res * tileWidth); + var dty = (top - y) / (res * tileHeight); + // index of tile in grid + var col = Math.floor(dtx); + var row = Math.floor(dty); + if (row >= 0 && row < numRows) { + var tile = this.grid[row][col]; + if (tile) { + data = { + tile: tile, + // pixel index within tile + i: Math.floor((dtx - col) * tileWidth), + j: Math.floor((dty - row) * tileHeight) + }; + } + } + } + return data; + }, + + /** + * Method: destroyTile + * + * Parameters: + * tile - {} + */ + destroyTile: function(tile) { + this.removeTileMonitoringHooks(tile); + tile.destroy(); + }, + + /** + * Method: getServerResolution + * Return the closest server-supported resolution. + * + * Parameters: + * resolution - {Number} The base resolution. If undefined the + * map resolution is used. + * + * Returns: + * {Number} The closest server resolution value. + */ + getServerResolution: function(resolution) { + var distance = Number.POSITIVE_INFINITY; + resolution = resolution || this.map.getResolution(); + if(this.serverResolutions && + OpenLayers.Util.indexOf(this.serverResolutions, resolution) === -1) { + var i, newDistance, newResolution, serverResolution; + for(i=this.serverResolutions.length-1; i>= 0; i--) { + newResolution = this.serverResolutions[i]; + newDistance = Math.abs(newResolution - resolution); + if (newDistance > distance) { + break; + } + distance = newDistance; + serverResolution = newResolution; + } + resolution = serverResolution; + } + return resolution; + }, + + /** + * Method: getServerZoom + * Return the zoom value corresponding to the best matching server + * resolution, taking into account and . + * + * Returns: + * {Number} The closest server supported zoom. This is not the map zoom + * level, but an index of the server's resolutions array. + */ + getServerZoom: function() { + var resolution = this.getServerResolution(); + return this.serverResolutions ? + OpenLayers.Util.indexOf(this.serverResolutions, resolution) : + this.map.getZoomForResolution(resolution) + (this.zoomOffset || 0); + }, + + /** + * Method: applyBackBuffer + * Create, insert, scale and position a back buffer for the layer. + * + * Parameters: + * resolution - {Number} The resolution to transition to. + */ + applyBackBuffer: function(resolution) { + if(this.backBufferTimerId !== null) { + this.removeBackBuffer(); + } + var backBuffer = this.backBuffer; + if(!backBuffer) { + backBuffer = this.createBackBuffer(); + if(!backBuffer) { + return; + } + if (resolution === this.gridResolution) { + this.div.insertBefore(backBuffer, this.div.firstChild); + } else { + this.map.baseLayer.div.parentNode.insertBefore(backBuffer, this.map.baseLayer.div); + } + this.backBuffer = backBuffer; + + // set some information in the instance for subsequent + // calls to applyBackBuffer where the same back buffer + // is reused + var topLeftTileBounds = this.grid[0][0].bounds; + this.backBufferLonLat = { + lon: topLeftTileBounds.left, + lat: topLeftTileBounds.top + }; + this.backBufferResolution = this.gridResolution; + } + + var ratio = this.backBufferResolution / resolution; + + // scale the tiles inside the back buffer + var tiles = backBuffer.childNodes, tile; + for (var i=tiles.length-1; i>=0; --i) { + tile = tiles[i]; + tile.style.top = ((ratio * tile._i * tile._h) | 0) + 'px'; + tile.style.left = ((ratio * tile._j * tile._w) | 0) + 'px'; + tile.style.width = Math.round(ratio * tile._w) + 'px'; + tile.style.height = Math.round(ratio * tile._h) + 'px'; + } + + // and position it (based on the grid's top-left corner) + var position = this.getViewPortPxFromLonLat( + this.backBufferLonLat, resolution); + var leftOffset = this.map.layerContainerOriginPx.x; + var topOffset = this.map.layerContainerOriginPx.y; + backBuffer.style.left = Math.round(position.x - leftOffset) + 'px'; + backBuffer.style.top = Math.round(position.y - topOffset) + 'px'; + }, + + /** + * Method: createBackBuffer + * Create a back buffer. + * + * Returns: + * {DOMElement} The DOM element for the back buffer, undefined if the + * grid isn't initialized yet. + */ + createBackBuffer: function() { + var backBuffer; + if(this.grid.length > 0) { + backBuffer = document.createElement('div'); + backBuffer.id = this.div.id + '_bb'; + backBuffer.className = 'olBackBuffer'; + backBuffer.style.position = 'absolute'; + var map = this.map; + backBuffer.style.zIndex = this.transitionEffect === 'resize' ? + this.getZIndex() - 1 : + // 'map-resize': + map.Z_INDEX_BASE.BaseLayer - + (map.getNumLayers() - map.getLayerIndex(this)); + for(var i=0, lenI=this.grid.length; i=0; --i) { + OpenLayers.Event.stopObserving(this._transitionElement, + this.transitionendEvents[i], this._removeBackBuffer); + } + delete this._transitionElement; + } + if(this.backBuffer) { + if (this.backBuffer.parentNode) { + this.backBuffer.parentNode.removeChild(this.backBuffer); + } + this.backBuffer = null; + this.backBufferResolution = null; + if(this.backBufferTimerId !== null) { + window.clearTimeout(this.backBufferTimerId); + this.backBufferTimerId = null; + } + } + }, + + /** + * Method: moveByPx + * Move the layer based on pixel vector. + * + * Parameters: + * dx - {Number} + * dy - {Number} + */ + moveByPx: function(dx, dy) { + if (!this.singleTile) { + this.moveGriddedTiles(); + } + }, + + /** + * APIMethod: setTileSize + * Check if we are in singleTile mode and if so, set the size as a ratio + * of the map size (as specified by the layer's 'ratio' property). + * + * Parameters: + * size - {} + */ + setTileSize: function(size) { + if (this.singleTile) { + size = this.map.getSize(); + size.h = parseInt(size.h * this.ratio, 10); + size.w = parseInt(size.w * this.ratio, 10); + } + OpenLayers.Layer.HTTPRequest.prototype.setTileSize.apply(this, [size]); + }, + + /** + * APIMethod: getTilesBounds + * Return the bounds of the tile grid. + * + * Returns: + * {} A Bounds object representing the bounds of all the + * currently loaded tiles (including those partially or not at all seen + * onscreen). + */ + getTilesBounds: function() { + var bounds = null; + + var length = this.grid.length; + if (length) { + var bottomLeftTileBounds = this.grid[length - 1][0].bounds, + width = this.grid[0].length * bottomLeftTileBounds.getWidth(), + height = this.grid.length * bottomLeftTileBounds.getHeight(); + + bounds = new OpenLayers.Bounds(bottomLeftTileBounds.left, + bottomLeftTileBounds.bottom, + bottomLeftTileBounds.left + width, + bottomLeftTileBounds.bottom + height); + } + return bounds; + }, + + /** + * Method: initSingleTile + * + * Parameters: + * bounds - {} + */ + initSingleTile: function(bounds) { + this.events.triggerEvent("retile"); + + //determine new tile bounds + var center = bounds.getCenterLonLat(); + var tileWidth = bounds.getWidth() * this.ratio; + var tileHeight = bounds.getHeight() * this.ratio; + + var tileBounds = + new OpenLayers.Bounds(center.lon - (tileWidth/2), + center.lat - (tileHeight/2), + center.lon + (tileWidth/2), + center.lat + (tileHeight/2)); + + var px = this.map.getLayerPxFromLonLat({ + lon: tileBounds.left, + lat: tileBounds.top + }); + + if (!this.grid.length) { + this.grid[0] = []; + } + + var tile = this.grid[0][0]; + if (!tile) { + tile = this.addTile(tileBounds, px); + + this.addTileMonitoringHooks(tile); + tile.draw(); + this.grid[0][0] = tile; + } else { + tile.moveTo(tileBounds, px); + } + + //remove all but our single tile + this.removeExcessTiles(1,1); + + // store the resolution of the grid + this.gridResolution = this.getServerResolution(); + }, + + /** + * Method: calculateGridLayout + * Generate parameters for the grid layout. + * + * Parameters: + * bounds - {|Object} OpenLayers.Bounds or an + * object with a 'left' and 'top' properties. + * origin - {|Object} OpenLayers.LonLat or an + * object with a 'lon' and 'lat' properties. + * resolution - {Number} + * + * Returns: + * {Object} Object containing properties tilelon, tilelat, startcol, + * startrow + */ + calculateGridLayout: function(bounds, origin, resolution) { + var tilelon = resolution * this.tileSize.w; + var tilelat = resolution * this.tileSize.h; + + var offsetlon = bounds.left - origin.lon; + var tilecol = Math.floor(offsetlon/tilelon) - this.buffer; + + var rowSign = this.rowSign; + + var offsetlat = rowSign * (origin.lat - bounds.top + tilelat); + var tilerow = Math[~rowSign ? 'floor' : 'ceil'](offsetlat/tilelat) - this.buffer * rowSign; + + return { + tilelon: tilelon, tilelat: tilelat, + startcol: tilecol, startrow: tilerow + }; + + }, + + /** + * Method: getTileOrigin + * Determine the origin for aligning the grid of tiles. If a + * property is supplied, that will be returned. Otherwise, the origin + * will be derived from the layer's property. In this case, + * the tile origin will be the corner of the given by the + * property. + * + * Returns: + * {} The tile origin. + */ + getTileOrigin: function() { + var origin = this.tileOrigin; + if (!origin) { + var extent = this.getMaxExtent(); + var edges = ({ + "tl": ["left", "top"], + "tr": ["right", "top"], + "bl": ["left", "bottom"], + "br": ["right", "bottom"] + })[this.tileOriginCorner]; + origin = new OpenLayers.LonLat(extent[edges[0]], extent[edges[1]]); + } + return origin; + }, + + /** + * Method: getTileBoundsForGridIndex + * + * Parameters: + * row - {Number} The row of the grid + * col - {Number} The column of the grid + * + * Returns: + * {} The bounds for the tile at (row, col) + */ + getTileBoundsForGridIndex: function(row, col) { + var origin = this.getTileOrigin(); + var tileLayout = this.gridLayout; + var tilelon = tileLayout.tilelon; + var tilelat = tileLayout.tilelat; + var startcol = tileLayout.startcol; + var startrow = tileLayout.startrow; + var rowSign = this.rowSign; + return new OpenLayers.Bounds( + origin.lon + (startcol + col) * tilelon, + origin.lat - (startrow + row * rowSign) * tilelat * rowSign, + origin.lon + (startcol + col + 1) * tilelon, + origin.lat - (startrow + (row - 1) * rowSign) * tilelat * rowSign + ); + }, + + /** + * Method: initGriddedTiles + * + * Parameters: + * bounds - {} + */ + initGriddedTiles:function(bounds) { + this.events.triggerEvent("retile"); + + // work out mininum number of rows and columns; this is the number of + // tiles required to cover the viewport plus at least one for panning + + var viewSize = this.map.getSize(); + + var origin = this.getTileOrigin(); + var resolution = this.map.getResolution(), + serverResolution = this.getServerResolution(), + ratio = resolution / serverResolution, + tileSize = { + w: this.tileSize.w / ratio, + h: this.tileSize.h / ratio + }; + + var minRows = Math.ceil(viewSize.h/tileSize.h) + + 2 * this.buffer + 1; + var minCols = Math.ceil(viewSize.w/tileSize.w) + + 2 * this.buffer + 1; + + var tileLayout = this.calculateGridLayout(bounds, origin, serverResolution); + this.gridLayout = tileLayout; + + var tilelon = tileLayout.tilelon; + var tilelat = tileLayout.tilelat; + + var layerContainerDivLeft = this.map.layerContainerOriginPx.x; + var layerContainerDivTop = this.map.layerContainerOriginPx.y; + + var tileBounds = this.getTileBoundsForGridIndex(0, 0); + var startPx = this.map.getViewPortPxFromLonLat( + new OpenLayers.LonLat(tileBounds.left, tileBounds.top) + ); + startPx.x = Math.round(startPx.x) - layerContainerDivLeft; + startPx.y = Math.round(startPx.y) - layerContainerDivTop; + + var tileData = [], center = this.map.getCenter(); + + var rowidx = 0; + do { + var row = this.grid[rowidx]; + if (!row) { + row = []; + this.grid.push(row); + } + + var colidx = 0; + do { + tileBounds = this.getTileBoundsForGridIndex(rowidx, colidx); + var px = startPx.clone(); + px.x = px.x + colidx * Math.round(tileSize.w); + px.y = px.y + rowidx * Math.round(tileSize.h); + var tile = row[colidx]; + if (!tile) { + tile = this.addTile(tileBounds, px); + this.addTileMonitoringHooks(tile); + row.push(tile); + } else { + tile.moveTo(tileBounds, px, false); + } + var tileCenter = tileBounds.getCenterLonLat(); + tileData.push({ + tile: tile, + distance: Math.pow(tileCenter.lon - center.lon, 2) + + Math.pow(tileCenter.lat - center.lat, 2) + }); + + colidx += 1; + } while ((tileBounds.right <= bounds.right + tilelon * this.buffer) + || colidx < minCols); + + rowidx += 1; + } while((tileBounds.bottom >= bounds.bottom - tilelat * this.buffer) + || rowidx < minRows); + + //shave off exceess rows and colums + this.removeExcessTiles(rowidx, colidx); + + var resolution = this.getServerResolution(); + // store the resolution of the grid + this.gridResolution = resolution; + + //now actually draw the tiles + tileData.sort(function(a, b) { + return a.distance - b.distance; + }); + for (var i=0, ii=tileData.length; i} + */ + getMaxExtent: function() { + return this.maxExtent; + }, + + /** + * APIMethod: addTile + * Create a tile, initialize it, and add it to the layer div. + * + * Parameters + * bounds - {} + * position - {} + * + * Returns: + * {} The added OpenLayers.Tile + */ + addTile: function(bounds, position) { + var tile = new this.tileClass( + this, position, bounds, null, this.tileSize, this.tileOptions + ); + this.events.triggerEvent("addtile", {tile: tile}); + return tile; + }, + + /** + * Method: addTileMonitoringHooks + * This function takes a tile as input and adds the appropriate hooks to + * the tile so that the layer can keep track of the loading tiles. + * + * Parameters: + * tile - {} + */ + addTileMonitoringHooks: function(tile) { + + var replacingCls = 'olTileReplacing'; + + tile.onLoadStart = function() { + //if that was first tile then trigger a 'loadstart' on the layer + if (this.loading === false) { + this.loading = true; + this.events.triggerEvent("loadstart"); + } + this.events.triggerEvent("tileloadstart", {tile: tile}); + this.numLoadingTiles++; + if (!this.singleTile && this.backBuffer && this.gridResolution === this.backBufferResolution) { + OpenLayers.Element.addClass(tile.getTile(), replacingCls); + } + }; + + tile.onLoadEnd = function(evt) { + this.numLoadingTiles--; + var aborted = evt.type === 'unload'; + this.events.triggerEvent("tileloaded", { + tile: tile, + aborted: aborted + }); + if (!this.singleTile && !aborted && this.backBuffer && this.gridResolution === this.backBufferResolution) { + var tileDiv = tile.getTile(); + if (OpenLayers.Element.getStyle(tileDiv, 'display') === 'none') { + var bufferTile = document.getElementById(tile.id + '_bb'); + if (bufferTile) { + bufferTile.parentNode.removeChild(bufferTile); + } + } + OpenLayers.Element.removeClass(tileDiv, replacingCls); + } + //if that was the last tile, then trigger a 'loadend' on the layer + if (this.numLoadingTiles === 0) { + if (this.backBuffer) { + if (this.backBuffer.childNodes.length === 0) { + // no tiles transitioning, remove immediately + this.removeBackBuffer(); + } else { + // wait until transition has ended or delay has passed + this._transitionElement = aborted ? + this.div.lastChild : tile.imgDiv; + var transitionendEvents = this.transitionendEvents; + for (var i=transitionendEvents.length-1; i>=0; --i) { + OpenLayers.Event.observe(this._transitionElement, + transitionendEvents[i], + this._removeBackBuffer); + } + // the removal of the back buffer is delayed to prevent + // flash effects due to the animation of tile displaying + this.backBufferTimerId = window.setTimeout( + this._removeBackBuffer, this.removeBackBufferDelay + ); + } + } + this.loading = false; + this.events.triggerEvent("loadend"); + } + }; + + tile.onLoadError = function() { + this.events.triggerEvent("tileerror", {tile: tile}); + }; + + tile.events.on({ + "loadstart": tile.onLoadStart, + "loadend": tile.onLoadEnd, + "unload": tile.onLoadEnd, + "loaderror": tile.onLoadError, + scope: this + }); + }, + + /** + * Method: removeTileMonitoringHooks + * This function takes a tile as input and removes the tile hooks + * that were added in addTileMonitoringHooks() + * + * Parameters: + * tile - {} + */ + removeTileMonitoringHooks: function(tile) { + tile.unload(); + tile.events.un({ + "loadstart": tile.onLoadStart, + "loadend": tile.onLoadEnd, + "unload": tile.onLoadEnd, + "loaderror": tile.onLoadError, + scope: this + }); + }, + + /** + * Method: moveGriddedTiles + */ + moveGriddedTiles: function() { + var buffer = this.buffer + 1; + while(true) { + var tlTile = this.grid[0][0]; + var tlViewPort = { + x: tlTile.position.x + + this.map.layerContainerOriginPx.x, + y: tlTile.position.y + + this.map.layerContainerOriginPx.y + }; + var ratio = this.getServerResolution() / this.map.getResolution(); + var tileSize = { + w: Math.round(this.tileSize.w * ratio), + h: Math.round(this.tileSize.h * ratio) + }; + if (tlViewPort.x > -tileSize.w * (buffer - 1)) { + this.shiftColumn(true, tileSize); + } else if (tlViewPort.x < -tileSize.w * buffer) { + this.shiftColumn(false, tileSize); + } else if (tlViewPort.y > -tileSize.h * (buffer - 1)) { + this.shiftRow(true, tileSize); + } else if (tlViewPort.y < -tileSize.h * buffer) { + this.shiftRow(false, tileSize); + } else { + break; + } + } + }, + + /** + * Method: shiftRow + * Shifty grid work + * + * Parameters: + * prepend - {Boolean} if true, prepend to beginning. + * if false, then append to end + * tileSize - {Object} rendered tile size; object with w and h properties + */ + shiftRow: function(prepend, tileSize) { + var grid = this.grid; + var rowIndex = prepend ? 0 : (grid.length - 1); + var sign = prepend ? -1 : 1; + var rowSign = this.rowSign; + var tileLayout = this.gridLayout; + tileLayout.startrow += sign * rowSign; + + var modelRow = grid[rowIndex]; + var row = grid[prepend ? 'pop' : 'shift'](); + for (var i=0, len=row.length; i rows) { + var row = this.grid.pop(); + for (i=0, l=row.length; i columns) { + var row = this.grid[i]; + var tile = row.pop(); + this.destroyTile(tile); + } + } + }, + + /** + * Method: onMapResize + * For singleTile layers, this will set a new tile size according to the + * dimensions of the map pane. + */ + onMapResize: function() { + if (this.singleTile) { + this.clearGrid(); + this.setTileSize(); + } + }, + + /** + * APIMethod: getTileBounds + * Returns The tile bounds for a layer given a pixel location. + * + * Parameters: + * viewPortPx - {} The location in the viewport. + * + * Returns: + * {} Bounds of the tile at the given pixel location. + */ + getTileBounds: function(viewPortPx) { + var maxExtent = this.maxExtent; + var resolution = this.getResolution(); + var tileMapWidth = resolution * this.tileSize.w; + var tileMapHeight = resolution * this.tileSize.h; + var mapPoint = this.getLonLatFromViewPortPx(viewPortPx); + var tileLeft = maxExtent.left + (tileMapWidth * + Math.floor((mapPoint.lon - + maxExtent.left) / + tileMapWidth)); + var tileBottom = maxExtent.bottom + (tileMapHeight * + Math.floor((mapPoint.lat - + maxExtent.bottom) / + tileMapHeight)); + return new OpenLayers.Bounds(tileLeft, tileBottom, + tileLeft + tileMapWidth, + tileBottom + tileMapHeight); + }, + + CLASS_NAME: "OpenLayers.Layer.Grid" +}); +/* ====================================================================== + OpenLayers/Layer/XYZ.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Layer/Grid.js + */ + +/** + * Class: OpenLayers.Layer.XYZ + * The XYZ class is designed to make it easier for people who have tiles + * arranged by a standard XYZ grid. + * + * Inherits from: + * - + */ +OpenLayers.Layer.XYZ = OpenLayers.Class(OpenLayers.Layer.Grid, { + + /** + * APIProperty: isBaseLayer + * Default is true, as this is designed to be a base tile source. + */ + isBaseLayer: true, + + /** + * APIProperty: sphericalMercator + * Whether the tile extents should be set to the defaults for + * spherical mercator. Useful for things like OpenStreetMap. + * Default is false, except for the OSM subclass. + */ + sphericalMercator: false, + + /** + * APIProperty: zoomOffset + * {Number} If your cache has more zoom levels than you want to provide + * access to with this layer, supply a zoomOffset. This zoom offset + * is added to the current map zoom level to determine the level + * for a requested tile. For example, if you supply a zoomOffset + * of 3, when the map is at the zoom 0, tiles will be requested from + * level 3 of your cache. Default is 0 (assumes cache level and map + * zoom are equivalent). Using is an alternative to + * setting if you only want to expose a subset + * of the server resolutions. + */ + zoomOffset: 0, + + /** + * APIProperty: serverResolutions + * {Array} A list of all resolutions available on the server. Only set this + * property if the map resolutions differ from the server. This + * property serves two purposes. (a) can include + * resolutions that the server supports and that you don't want to + * provide with this layer; you can also look at , which is + * an alternative to for that specific purpose. + * (b) The map can work with resolutions that aren't supported by + * the server, i.e. that aren't in . When the + * map is displayed in such a resolution data for the closest + * server-supported resolution is loaded and the layer div is + * stretched as necessary. + */ + serverResolutions: null, + + /** + * Constructor: OpenLayers.Layer.XYZ + * + * Parameters: + * name - {String} + * url - {String} + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function(name, url, options) { + if (options && options.sphericalMercator || this.sphericalMercator) { + options = OpenLayers.Util.extend({ + projection: "EPSG:900913", + numZoomLevels: 19 + }, options); + } + OpenLayers.Layer.Grid.prototype.initialize.apply(this, [ + name || this.name, url || this.url, {}, options + ]); + }, + + /** + * APIMethod: clone + * Create a clone of this layer + * + * Parameters: + * obj - {Object} Is this ever used? + * + * Returns: + * {} An exact clone of this OpenLayers.Layer.XYZ + */ + clone: function (obj) { + + if (obj == null) { + obj = new OpenLayers.Layer.XYZ(this.name, + this.url, + this.getOptions()); + } + + //get all additions from superclasses + obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]); + + return obj; + }, + + /** + * Method: getURL + * + * Parameters: + * bounds - {} + * + * Returns: + * {String} A string with the layer's url and parameters and also the + * passed-in bounds and appropriate tile size specified as + * parameters + */ + getURL: function (bounds) { + var xyz = this.getXYZ(bounds); + var url = this.url; + if (OpenLayers.Util.isArray(url)) { + var s = '' + xyz.x + xyz.y + xyz.z; + url = this.selectUrl(s, url); + } + + return OpenLayers.String.format(url, xyz); + }, + + /** + * Method: getXYZ + * Calculates x, y and z for the given bounds. + * + * Parameters: + * bounds - {} + * + * Returns: + * {Object} - an object with x, y and z properties. + */ + getXYZ: function(bounds) { + var res = this.getServerResolution(); + var x = Math.round((bounds.left - this.maxExtent.left) / + (res * this.tileSize.w)); + var y = Math.round((this.maxExtent.top - bounds.top) / + (res * this.tileSize.h)); + var z = this.getServerZoom(); + + if (this.wrapDateLine) { + var limit = Math.pow(2, z); + x = ((x % limit) + limit) % limit; + } + + return {'x': x, 'y': y, 'z': z}; + }, + + /* APIMethod: setMap + * When the layer is added to a map, then we can fetch our origin + * (if we don't have one.) + * + * Parameters: + * map - {} + */ + setMap: function(map) { + OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments); + if (!this.tileOrigin) { + this.tileOrigin = new OpenLayers.LonLat(this.maxExtent.left, + this.maxExtent.bottom); + } + }, + + CLASS_NAME: "OpenLayers.Layer.XYZ" +}); +/* ====================================================================== + OpenLayers/Layer/OSM.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Layer/XYZ.js + */ + +/** + * Class: OpenLayers.Layer.OSM + * This layer allows accessing OpenStreetMap tiles. By default the OpenStreetMap + * hosted tile.openstreetmap.org Mapnik tileset is used. If you wish to use + * a different layer instead, you need to provide a different + * URL to the constructor. Here's an example for using OpenCycleMap: + * + * (code) + * new OpenLayers.Layer.OSM("OpenCycleMap", + * ["http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png", + * "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png", + * "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png"]); + * (end) + * + * Inherits from: + * - + */ +OpenLayers.Layer.OSM = OpenLayers.Class(OpenLayers.Layer.XYZ, { + + /** + * APIProperty: name + * {String} The layer name. Defaults to "OpenStreetMap" if the first + * argument to the constructor is null or undefined. + */ + name: "OpenStreetMap", + + /** + * APIProperty: url + * {String} The tileset URL scheme. Defaults to + * : http://[a|b|c].tile.openstreetmap.org/${z}/${x}/${y}.png + * (the official OSM tileset) if the second argument to the constructor + * is null or undefined. To use another tileset you can have something + * like this: + * (code) + * new OpenLayers.Layer.OSM("OpenCycleMap", + * ["http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png", + * "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png", + * "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png"]); + * (end) + */ + url: [ + 'http://a.tile.openstreetmap.org/${z}/${x}/${y}.png', + 'http://b.tile.openstreetmap.org/${z}/${x}/${y}.png', + 'http://c.tile.openstreetmap.org/${z}/${x}/${y}.png' + ], + + /** + * Property: attribution + * {String} The layer attribution. + */ + attribution: "© OpenStreetMap contributors", + + /** + * Property: sphericalMercator + * {Boolean} + */ + sphericalMercator: true, + + /** + * Property: wrapDateLine + * {Boolean} + */ + wrapDateLine: true, + + /** APIProperty: tileOptions + * {Object} optional configuration options for instances + * created by this Layer. Default is + * + * (code) + * {crossOriginKeyword: 'anonymous'} + * (end) + * + * When using OSM tilesets other than the default ones, it may be + * necessary to set this to + * + * (code) + * {crossOriginKeyword: null} + * (end) + * + * if the server does not send Access-Control-Allow-Origin headers. + */ + tileOptions: null, + + /** + * Constructor: OpenLayers.Layer.OSM + * + * Parameters: + * name - {String} The layer name. + * url - {String} The tileset URL scheme. + * options - {Object} Configuration options for the layer. Any inherited + * layer option can be set in this object (e.g. + * ). + */ + initialize: function(name, url, options) { + OpenLayers.Layer.XYZ.prototype.initialize.apply(this, arguments); + this.tileOptions = OpenLayers.Util.extend({ + crossOriginKeyword: 'anonymous' + }, this.options && this.options.tileOptions); + }, + + /** + * Method: clone + */ + clone: function(obj) { + if (obj == null) { + obj = new OpenLayers.Layer.OSM( + this.name, this.url, this.getOptions()); + } + obj = OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]); + return obj; + }, + + CLASS_NAME: "OpenLayers.Layer.OSM" +}); +/* ====================================================================== + OpenLayers/Renderer.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/BaseTypes/Class.js + */ + +/** + * Class: OpenLayers.Renderer + * This is the base class for all renderers. + * + * This is based on a merger code written by Paul Spencer and Bertil Chapuis. + * It is largely composed of virtual functions that are to be implemented + * in technology-specific subclasses, but there is some generic code too. + * + * The functions that *are* implemented here merely deal with the maintenance + * of the size and extent variables, as well as the cached 'resolution' + * value. + * + * A note to the user that all subclasses should use getResolution() instead + * of directly accessing this.resolution in order to correctly use the + * cacheing system. + * + */ +OpenLayers.Renderer = OpenLayers.Class({ + + /** + * Property: container + * {DOMElement} + */ + container: null, + + /** + * Property: root + * {DOMElement} + */ + root: null, + + /** + * Property: extent + * {} + */ + extent: null, + + /** + * Property: locked + * {Boolean} If the renderer is currently in a state where many things + * are changing, the 'locked' property is set to true. This means + * that renderers can expect at least one more drawFeature event to be + * called with the 'locked' property set to 'true': In some renderers, + * this might make sense to use as a 'only update local information' + * flag. + */ + locked: false, + + /** + * Property: size + * {} + */ + size: null, + + /** + * Property: resolution + * {Float} cache of current map resolution + */ + resolution: null, + + /** + * Property: map + * {} Reference to the map -- this is set in Vector's setMap() + */ + map: null, + + /** + * Property: featureDx + * {Number} Feature offset in x direction. Will be calculated for and + * applied to the current feature while rendering (see + * ). + */ + featureDx: 0, + + /** + * Constructor: OpenLayers.Renderer + * + * Parameters: + * containerID - {} + * options - {Object} options for this renderer. See sublcasses for + * supported options. + */ + initialize: function(containerID, options) { + this.container = OpenLayers.Util.getElement(containerID); + OpenLayers.Util.extend(this, options); + }, + + /** + * APIMethod: destroy + */ + destroy: function() { + this.container = null; + this.extent = null; + this.size = null; + this.resolution = null; + this.map = null; + }, + + /** + * APIMethod: supported + * This should be overridden by specific subclasses + * + * Returns: + * {Boolean} Whether or not the browser supports the renderer class + */ + supported: function() { + return false; + }, + + /** + * Method: setExtent + * Set the visible part of the layer. + * + * Resolution has probably changed, so we nullify the resolution + * cache (this.resolution) -- this way it will be re-computed when + * next it is needed. + * We nullify the resolution cache (this.resolution) if resolutionChanged + * is set to true - this way it will be re-computed on the next + * getResolution() request. + * + * Parameters: + * extent - {} + * resolutionChanged - {Boolean} + * + * Returns: + * {Boolean} true to notify the layer that the new extent does not exceed + * the coordinate range, and the features will not need to be redrawn. + * False otherwise. + */ + setExtent: function(extent, resolutionChanged) { + this.extent = extent.clone(); + if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) { + var ratio = extent.getWidth() / this.map.getExtent().getWidth(), + extent = extent.scale(1 / ratio); + this.extent = extent.wrapDateLine(this.map.getMaxExtent()).scale(ratio); + } + if (resolutionChanged) { + this.resolution = null; + } + return true; + }, + + /** + * Method: setSize + * Sets the size of the drawing surface. + * + * Resolution has probably changed, so we nullify the resolution + * cache (this.resolution) -- this way it will be re-computed when + * next it is needed. + * + * Parameters: + * size - {} + */ + setSize: function(size) { + this.size = size.clone(); + this.resolution = null; + }, + + /** + * Method: getResolution + * Uses cached copy of resolution if available to minimize computing + * + * Returns: + * {Float} The current map's resolution + */ + getResolution: function() { + this.resolution = this.resolution || this.map.getResolution(); + return this.resolution; + }, + + /** + * Method: drawFeature + * Draw the feature. The optional style argument can be used + * to override the feature's own style. This method should only + * be called from layer.drawFeature(). + * + * Parameters: + * feature - {} + * style - {} + * + * Returns: + * {Boolean} true if the feature has been drawn completely, false if not, + * undefined if the feature had no geometry + */ + drawFeature: function(feature, style) { + if(style == null) { + style = feature.style; + } + if (feature.geometry) { + var bounds = feature.geometry.getBounds(); + if(bounds) { + var worldBounds; + if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) { + worldBounds = this.map.getMaxExtent(); + } + if (!bounds.intersectsBounds(this.extent, {worldBounds: worldBounds})) { + style = {display: "none"}; + } else { + this.calculateFeatureDx(bounds, worldBounds); + } + var rendered = this.drawGeometry(feature.geometry, style, feature.id); + if(style.display != "none" && style.label && rendered !== false) { + + var location = feature.geometry.getCentroid(); + if(style.labelXOffset || style.labelYOffset) { + var xOffset = isNaN(style.labelXOffset) ? 0 : style.labelXOffset; + var yOffset = isNaN(style.labelYOffset) ? 0 : style.labelYOffset; + var res = this.getResolution(); + location.move(xOffset*res, yOffset*res); + } + this.drawText(feature.id, style, location); + } else { + this.removeText(feature.id); + } + return rendered; + } + } + }, + + /** + * Method: calculateFeatureDx + * {Number} Calculates the feature offset in x direction. Looking at the + * center of the feature bounds and the renderer extent, we calculate how + * many world widths the two are away from each other. This distance is + * used to shift the feature as close as possible to the center of the + * current enderer extent, which ensures that the feature is visible in the + * current viewport. + * + * Parameters: + * bounds - {} Bounds of the feature + * worldBounds - {} Bounds of the world + */ + calculateFeatureDx: function(bounds, worldBounds) { + this.featureDx = 0; + if (worldBounds) { + var worldWidth = worldBounds.getWidth(), + rendererCenterX = (this.extent.left + this.extent.right) / 2, + featureCenterX = (bounds.left + bounds.right) / 2, + worldsAway = Math.round((featureCenterX - rendererCenterX) / worldWidth); + this.featureDx = worldsAway * worldWidth; + } + }, + + /** + * Method: drawGeometry + * + * Draw a geometry. This should only be called from the renderer itself. + * Use layer.drawFeature() from outside the renderer. + * virtual function + * + * Parameters: + * geometry - {} + * style - {Object} + * featureId - {} + */ + drawGeometry: function(geometry, style, featureId) {}, + + /** + * Method: drawText + * Function for drawing text labels. + * This method is only called by the renderer itself. + * + * Parameters: + * featureId - {String} + * style - + * location - {} + */ + drawText: function(featureId, style, location) {}, + + /** + * Method: removeText + * Function for removing text labels. + * This method is only called by the renderer itself. + * + * Parameters: + * featureId - {String} + */ + removeText: function(featureId) {}, + + /** + * Method: clear + * Clear all vectors from the renderer. + * virtual function. + */ + clear: function() {}, + + /** + * Method: getFeatureIdFromEvent + * Returns a feature id from an event on the renderer. + * How this happens is specific to the renderer. This should be + * called from layer.getFeatureFromEvent(). + * Virtual function. + * + * Parameters: + * evt - {} + * + * Returns: + * {String} A feature id or undefined. + */ + getFeatureIdFromEvent: function(evt) {}, + + /** + * Method: eraseFeatures + * This is called by the layer to erase features + * + * Parameters: + * features - {Array()} + */ + eraseFeatures: function(features) { + if(!(OpenLayers.Util.isArray(features))) { + features = [features]; + } + for(var i=0, len=features.length; i} + * featureId - {String} + */ + eraseGeometry: function(geometry, featureId) {}, + + /** + * Method: moveRoot + * moves this renderer's root to a (different) renderer. + * To be implemented by subclasses that require a common renderer root for + * feature selection. + * + * Parameters: + * renderer - {} target renderer for the moved root + */ + moveRoot: function(renderer) {}, + + /** + * Method: getRenderLayerId + * Gets the layer that this renderer's output appears on. If moveRoot was + * used, this will be different from the id of the layer containing the + * features rendered by this renderer. + * + * Returns: + * {String} the id of the output layer. + */ + getRenderLayerId: function() { + return this.container.id; + }, + + /** + * Method: applyDefaultSymbolizer + * + * Parameters: + * symbolizer - {Object} + * + * Returns: + * {Object} + */ + applyDefaultSymbolizer: function(symbolizer) { + var result = OpenLayers.Util.extend({}, + OpenLayers.Renderer.defaultSymbolizer); + if(symbolizer.stroke === false) { + delete result.strokeWidth; + delete result.strokeColor; + } + if(symbolizer.fill === false) { + delete result.fillColor; + } + OpenLayers.Util.extend(result, symbolizer); + return result; + }, + + CLASS_NAME: "OpenLayers.Renderer" +}); + +/** + * Constant: OpenLayers.Renderer.defaultSymbolizer + * {Object} Properties from this symbolizer will be applied to symbolizers + * with missing properties. This can also be used to set a global + * symbolizer default in OpenLayers. To be SLD 1.x compliant, add the + * following code before rendering any vector features: + * (code) + * OpenLayers.Renderer.defaultSymbolizer = { + * fillColor: "#808080", + * fillOpacity: 1, + * strokeColor: "#000000", + * strokeOpacity: 1, + * strokeWidth: 1, + * pointRadius: 3, + * graphicName: "square" + * }; + * (end) + */ +OpenLayers.Renderer.defaultSymbolizer = { + fillColor: "#000000", + strokeColor: "#000000", + strokeWidth: 2, + fillOpacity: 1, + strokeOpacity: 1, + pointRadius: 0, + labelAlign: 'cm' +}; + + + +/** + * Constant: OpenLayers.Renderer.symbol + * Coordinate arrays for well known (named) symbols. + */ +OpenLayers.Renderer.symbol = { + "star": [350,75, 379,161, 469,161, 397,215, 423,301, 350,250, 277,301, + 303,215, 231,161, 321,161, 350,75], + "cross": [4,0, 6,0, 6,4, 10,4, 10,6, 6,6, 6,10, 4,10, 4,6, 0,6, 0,4, 4,4, + 4,0], + "x": [0,0, 25,0, 50,35, 75,0, 100,0, 65,50, 100,100, 75,100, 50,65, 25,100, 0,100, 35,50, 0,0], + "square": [0,0, 0,1, 1,1, 1,0, 0,0], + "triangle": [0,10, 10,10, 5,0, 0,10] +}; +/* ====================================================================== + OpenLayers/Renderer/Canvas.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Renderer.js + */ + +/** + * Class: OpenLayers.Renderer.Canvas + * A renderer based on the 2D 'canvas' drawing element. + * + * Inherits: + * - + */ +OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, { + + /** + * APIProperty: hitDetection + * {Boolean} Allow for hit detection of features. Default is true. + */ + hitDetection: true, + + /** + * Property: hitOverflow + * {Number} The method for converting feature identifiers to color values + * supports 16777215 sequential values. Two features cannot be + * predictably detected if their identifiers differ by more than this + * value. The hitOverflow allows for bigger numbers (but the + * difference in values is still limited). + */ + hitOverflow: 0, + + /** + * Property: canvas + * {Canvas} The canvas context object. + */ + canvas: null, + + /** + * Property: features + * {Object} Internal object of feature/style pairs for use in redrawing the layer. + */ + features: null, + + /** + * Property: pendingRedraw + * {Boolean} The renderer needs a redraw call to render features added while + * the renderer was locked. + */ + pendingRedraw: false, + + /** + * Property: cachedSymbolBounds + * {Object} Internal cache of calculated symbol extents. + */ + cachedSymbolBounds: {}, + + /** + * Constructor: OpenLayers.Renderer.Canvas + * + * Parameters: + * containerID - {} + * options - {Object} Optional properties to be set on the renderer. + */ + initialize: function(containerID, options) { + OpenLayers.Renderer.prototype.initialize.apply(this, arguments); + this.root = document.createElement("canvas"); + this.container.appendChild(this.root); + this.canvas = this.root.getContext("2d"); + this.features = {}; + if (this.hitDetection) { + this.hitCanvas = document.createElement("canvas"); + this.hitContext = this.hitCanvas.getContext("2d"); + } + }, + + /** + * Method: setExtent + * Set the visible part of the layer. + * + * Parameters: + * extent - {} + * resolutionChanged - {Boolean} + * + * Returns: + * {Boolean} true to notify the layer that the new extent does not exceed + * the coordinate range, and the features will not need to be redrawn. + * False otherwise. + */ + setExtent: function() { + OpenLayers.Renderer.prototype.setExtent.apply(this, arguments); + // always redraw features + return false; + }, + + /** + * Method: eraseGeometry + * Erase a geometry from the renderer. Because the Canvas renderer has + * 'memory' of the features that it has drawn, we have to remove the + * feature so it doesn't redraw. + * + * Parameters: + * geometry - {} + * featureId - {String} + */ + eraseGeometry: function(geometry, featureId) { + this.eraseFeatures(this.features[featureId][0]); + }, + + /** + * APIMethod: supported + * + * Returns: + * {Boolean} Whether or not the browser supports the renderer class + */ + supported: function() { + return OpenLayers.CANVAS_SUPPORTED; + }, + + /** + * Method: setSize + * Sets the size of the drawing surface. + * + * Once the size is updated, redraw the canvas. + * + * Parameters: + * size - {} + */ + setSize: function(size) { + this.size = size.clone(); + var root = this.root; + root.style.width = size.w + "px"; + root.style.height = size.h + "px"; + root.width = size.w; + root.height = size.h; + this.resolution = null; + if (this.hitDetection) { + var hitCanvas = this.hitCanvas; + hitCanvas.style.width = size.w + "px"; + hitCanvas.style.height = size.h + "px"; + hitCanvas.width = size.w; + hitCanvas.height = size.h; + } + }, + + /** + * Method: drawFeature + * Draw the feature. Stores the feature in the features list, + * then redraws the layer. + * + * Parameters: + * feature - {} + * style - {} + * + * Returns: + * {Boolean} The feature has been drawn completely. If the feature has no + * geometry, undefined will be returned. If the feature is not rendered + * for other reasons, false will be returned. + */ + drawFeature: function(feature, style) { + var rendered; + if (feature.geometry) { + style = this.applyDefaultSymbolizer(style || feature.style); + // don't render if display none or feature outside extent + var bounds = feature.geometry.getBounds(); + + var worldBounds; + if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) { + worldBounds = this.map.getMaxExtent(); + } + + var intersects = bounds && bounds.intersectsBounds(this.extent, {worldBounds: worldBounds}); + + rendered = (style.display !== "none") && !!bounds && intersects; + if (rendered) { + // keep track of what we have rendered for redraw + this.features[feature.id] = [feature, style]; + } + else { + // remove from features tracked for redraw + delete(this.features[feature.id]); + } + this.pendingRedraw = true; + } + if (this.pendingRedraw && !this.locked) { + this.redraw(); + this.pendingRedraw = false; + } + return rendered; + }, + + /** + * Method: drawGeometry + * Used when looping (in redraw) over the features; draws + * the canvas. + * + * Parameters: + * geometry - {} + * style - {Object} + */ + drawGeometry: function(geometry, style, featureId) { + var className = geometry.CLASS_NAME; + if ((className == "OpenLayers.Geometry.Collection") || + (className == "OpenLayers.Geometry.MultiPoint") || + (className == "OpenLayers.Geometry.MultiLineString") || + (className == "OpenLayers.Geometry.MultiPolygon")) { + for (var i = 0; i < geometry.components.length; i++) { + this.drawGeometry(geometry.components[i], style, featureId); + } + return; + } + switch (geometry.CLASS_NAME) { + case "OpenLayers.Geometry.Point": + this.drawPoint(geometry, style, featureId); + break; + case "OpenLayers.Geometry.LineString": + this.drawLineString(geometry, style, featureId); + break; + case "OpenLayers.Geometry.LinearRing": + this.drawLinearRing(geometry, style, featureId); + break; + case "OpenLayers.Geometry.Polygon": + this.drawPolygon(geometry, style, featureId); + break; + default: + break; + } + }, + + /** + * Method: drawExternalGraphic + * Called to draw External graphics. + * + * Parameters: + * geometry - {} + * style - {Object} + * featureId - {String} + */ + drawExternalGraphic: function(geometry, style, featureId) { + var img = new Image(); + + var title = style.title || style.graphicTitle; + if (title) { + img.title = title; + } + + var width = style.graphicWidth || style.graphicHeight; + var height = style.graphicHeight || style.graphicWidth; + width = width ? width : style.pointRadius * 2; + height = height ? height : style.pointRadius * 2; + var xOffset = (style.graphicXOffset != undefined) ? + style.graphicXOffset : -(0.5 * width); + var yOffset = (style.graphicYOffset != undefined) ? + style.graphicYOffset : -(0.5 * height); + + var opacity = style.graphicOpacity || style.fillOpacity; + + var onLoad = function() { + if(!this.features[featureId]) { + return; + } + var pt = this.getLocalXY(geometry); + var p0 = pt[0]; + var p1 = pt[1]; + if(!isNaN(p0) && !isNaN(p1)) { + var x = (p0 + xOffset) | 0; + var y = (p1 + yOffset) | 0; + var canvas = this.canvas; + canvas.globalAlpha = opacity; + var factor = OpenLayers.Renderer.Canvas.drawImageScaleFactor || + (OpenLayers.Renderer.Canvas.drawImageScaleFactor = + /android 2.1/.test(navigator.userAgent.toLowerCase()) ? + // 320 is the screen width of the G1 phone, for + // which drawImage works out of the box. + 320 / window.screen.width : 1 + ); + canvas.drawImage( + img, x*factor, y*factor, width*factor, height*factor + ); + if (this.hitDetection) { + this.setHitContextStyle("fill", featureId); + this.hitContext.fillRect(x, y, width, height); + } + } + }; + + img.onload = OpenLayers.Function.bind(onLoad, this); + img.src = style.externalGraphic; + }, + + /** + * Method: drawNamedSymbol + * Called to draw Well Known Graphic Symbol Name. + * This method is only called by the renderer itself. + * + * Parameters: + * geometry - {} + * style - {Object} + * featureId - {String} + */ + drawNamedSymbol: function(geometry, style, featureId) { + var x, y, cx, cy, i, symbolBounds, scaling, angle; + var unscaledStrokeWidth; + var deg2rad = Math.PI / 180.0; + + var symbol = OpenLayers.Renderer.symbol[style.graphicName]; + + if (!symbol) { + throw new Error(style.graphicName + ' is not a valid symbol name'); + } + + if (!symbol.length || symbol.length < 2) return; + + var pt = this.getLocalXY(geometry); + var p0 = pt[0]; + var p1 = pt[1]; + + if (isNaN(p0) || isNaN(p1)) return; + + // Use rounded line caps + this.canvas.lineCap = "round"; + this.canvas.lineJoin = "round"; + + if (this.hitDetection) { + this.hitContext.lineCap = "round"; + this.hitContext.lineJoin = "round"; + } + + // Scale and rotate symbols, using precalculated bounds whenever possible. + if (style.graphicName in this.cachedSymbolBounds) { + symbolBounds = this.cachedSymbolBounds[style.graphicName]; + } else { + symbolBounds = new OpenLayers.Bounds(); + for(i = 0; i < symbol.length; i+=2) { + symbolBounds.extend(new OpenLayers.LonLat(symbol[i], symbol[i+1])); + } + this.cachedSymbolBounds[style.graphicName] = symbolBounds; + } + + // Push symbol scaling, translation and rotation onto the transformation stack in reverse order. + // Don't forget to apply all canvas transformations to the hitContext canvas as well(!) + this.canvas.save(); + if (this.hitDetection) { this.hitContext.save(); } + + // Step 3: place symbol at the desired location + this.canvas.translate(p0,p1); + if (this.hitDetection) { this.hitContext.translate(p0,p1); } + + // Step 2a. rotate the symbol if necessary + angle = deg2rad * style.rotation; // will be NaN when style.rotation is undefined. + if (!isNaN(angle)) { + this.canvas.rotate(angle); + if (this.hitDetection) { this.hitContext.rotate(angle); } + } + + // // Step 2: scale symbol such that pointRadius equals half the maximum symbol dimension. + scaling = 2.0 * style.pointRadius / Math.max(symbolBounds.getWidth(), symbolBounds.getHeight()); + this.canvas.scale(scaling,scaling); + if (this.hitDetection) { this.hitContext.scale(scaling,scaling); } + + // Step 1: center the symbol at the origin + cx = symbolBounds.getCenterLonLat().lon; + cy = symbolBounds.getCenterLonLat().lat; + this.canvas.translate(-cx,-cy); + if (this.hitDetection) { this.hitContext.translate(-cx,-cy); } + + // Don't forget to scale stroke widths, because they are affected by canvas scale transformations as well(!) + // Alternative: scale symbol coordinates manually, so stroke width scaling is not needed anymore. + unscaledStrokeWidth = style.strokeWidth; + style.strokeWidth = unscaledStrokeWidth / scaling; + + if (style.fill !== false) { + this.setCanvasStyle("fill", style); + this.canvas.beginPath(); + for (i=0; i= 16777216) { + this.hitOverflow = id - 16777215; + id = id % 16777216 + 1; + } + var hex = "000000" + id.toString(16); + var len = hex.length; + hex = "#" + hex.substring(len-6, len); + return hex; + }, + + /** + * Method: setHitContextStyle + * Prepare the hit canvas for drawing by setting various global settings. + * + * Parameters: + * type - {String} one of 'stroke', 'fill', or 'reset' + * featureId - {String} The feature id. + * symbolizer - {} The symbolizer. + */ + setHitContextStyle: function(type, featureId, symbolizer, strokeScaling) { + var hex = this.featureIdToHex(featureId); + if (type == "fill") { + this.hitContext.globalAlpha = 1.0; + this.hitContext.fillStyle = hex; + } else if (type == "stroke") { + this.hitContext.globalAlpha = 1.0; + this.hitContext.strokeStyle = hex; + // bump up stroke width to deal with antialiasing. If strokeScaling is defined, we're rendering a symbol + // on a transformed canvas, so the antialias width bump has to scale as well. + if (typeof strokeScaling === "undefined") { + this.hitContext.lineWidth = symbolizer.strokeWidth + 2; + } else { + if (!isNaN(strokeScaling)) { this.hitContext.lineWidth = symbolizer.strokeWidth + 2.0 / strokeScaling; } + } + } else { + this.hitContext.globalAlpha = 0; + this.hitContext.lineWidth = 1; + } + }, + + /** + * Method: drawPoint + * This method is only called by the renderer itself. + * + * Parameters: + * geometry - {} + * style - {Object} + * featureId - {String} + */ + drawPoint: function(geometry, style, featureId) { + if(style.graphic !== false) { + if(style.externalGraphic) { + this.drawExternalGraphic(geometry, style, featureId); + } else if (style.graphicName && (style.graphicName != "circle")) { + this.drawNamedSymbol(geometry, style, featureId); + } else { + var pt = this.getLocalXY(geometry); + var p0 = pt[0]; + var p1 = pt[1]; + if(!isNaN(p0) && !isNaN(p1)) { + var twoPi = Math.PI*2; + var radius = style.pointRadius; + if(style.fill !== false) { + this.setCanvasStyle("fill", style); + this.canvas.beginPath(); + this.canvas.arc(p0, p1, radius, 0, twoPi, true); + this.canvas.fill(); + if (this.hitDetection) { + this.setHitContextStyle("fill", featureId, style); + this.hitContext.beginPath(); + this.hitContext.arc(p0, p1, radius, 0, twoPi, true); + this.hitContext.fill(); + } + } + + if(style.stroke !== false) { + this.setCanvasStyle("stroke", style); + this.canvas.beginPath(); + this.canvas.arc(p0, p1, radius, 0, twoPi, true); + this.canvas.stroke(); + if (this.hitDetection) { + this.setHitContextStyle("stroke", featureId, style); + this.hitContext.beginPath(); + this.hitContext.arc(p0, p1, radius, 0, twoPi, true); + this.hitContext.stroke(); + } + this.setCanvasStyle("reset"); + } + } + } + } + }, + + /** + * Method: drawLineString + * This method is only called by the renderer itself. + * + * Parameters: + * geometry - {} + * style - {Object} + * featureId - {String} + */ + drawLineString: function(geometry, style, featureId) { + style = OpenLayers.Util.applyDefaults({fill: false}, style); + this.drawLinearRing(geometry, style, featureId); + }, + + /** + * Method: drawLinearRing + * This method is only called by the renderer itself. + * + * Parameters: + * geometry - {} + * style - {Object} + * featureId - {String} + */ + drawLinearRing: function(geometry, style, featureId) { + if (style.fill !== false) { + this.setCanvasStyle("fill", style); + this.renderPath(this.canvas, geometry, style, featureId, "fill"); + if (this.hitDetection) { + this.setHitContextStyle("fill", featureId, style); + this.renderPath(this.hitContext, geometry, style, featureId, "fill"); + } + } + if (style.stroke !== false) { + this.setCanvasStyle("stroke", style); + this.renderPath(this.canvas, geometry, style, featureId, "stroke"); + if (this.hitDetection) { + this.setHitContextStyle("stroke", featureId, style); + this.renderPath(this.hitContext, geometry, style, featureId, "stroke"); + } + } + this.setCanvasStyle("reset"); + }, + + /** + * Method: renderPath + * Render a path with stroke and optional fill. + */ + renderPath: function(context, geometry, style, featureId, type) { + var components = geometry.components; + var len = components.length; + context.beginPath(); + var start = this.getLocalXY(components[0]); + var x = start[0]; + var y = start[1]; + if (!isNaN(x) && !isNaN(y)) { + context.moveTo(start[0], start[1]); + for (var i=1; i} + * style - {Object} + * featureId - {String} + */ + drawPolygon: function(geometry, style, featureId) { + var components = geometry.components; + var len = components.length; + this.drawLinearRing(components[0], style, featureId); + // erase inner rings + for (var i=1; i} + * style - {Object} + */ + drawText: function(location, style) { + var pt = this.getLocalXY(location); + + this.setCanvasStyle("reset"); + this.canvas.fillStyle = style.fontColor; + this.canvas.globalAlpha = style.fontOpacity || 1.0; + var fontStyle = [style.fontStyle ? style.fontStyle : "normal", + "normal", // "font-variant" not supported + style.fontWeight ? style.fontWeight : "normal", + style.fontSize ? style.fontSize : "1em", + style.fontFamily ? style.fontFamily : "sans-serif"].join(" "); + var labelRows = style.label.split('\n'); + var numRows = labelRows.length; + if (this.canvas.fillText) { + // HTML5 + this.canvas.font = fontStyle; + this.canvas.textAlign = + OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[0]] || + "center"; + this.canvas.textBaseline = + OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[1]] || + "middle"; + var vfactor = + OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[1]]; + if (vfactor == null) { + vfactor = -.5; + } + var lineHeight = + this.canvas.measureText('Mg').height || + this.canvas.measureText('xx').width; + pt[1] += lineHeight*vfactor*(numRows-1); + for (var i = 0; i < numRows; i++) { + if (style.labelOutlineWidth) { + this.canvas.save(); + this.canvas.globalAlpha = style.labelOutlineOpacity || style.fontOpacity || 1.0; + this.canvas.strokeStyle = style.labelOutlineColor; + this.canvas.lineWidth = style.labelOutlineWidth; + this.canvas.strokeText(labelRows[i], pt[0], pt[1] + (lineHeight*i) + 1); + this.canvas.restore(); + } + this.canvas.fillText(labelRows[i], pt[0], pt[1] + (lineHeight*i)); + } + } else if (this.canvas.mozDrawText) { + // Mozilla pre-Gecko1.9.1 (} + */ + getLocalXY: function(point) { + var resolution = this.getResolution(); + var extent = this.extent; + var x = ((point.x - this.featureDx) / resolution + (-extent.left / resolution)); + var y = ((extent.top / resolution) - point.y / resolution); + return [x, y]; + }, + + /** + * Method: clear + * Clear all vectors from the renderer. + */ + clear: function() { + var height = this.root.height; + var width = this.root.width; + this.canvas.clearRect(0, 0, width, height); + this.features = {}; + if (this.hitDetection) { + this.hitContext.clearRect(0, 0, width, height); + } + }, + + /** + * Method: getFeatureIdFromEvent + * Returns a feature id from an event on the renderer. + * + * Parameters: + * evt - {} + * + * Returns: + * {)} + */ + eraseFeatures: function(features) { + if(!(OpenLayers.Util.isArray(features))) { + features = [features]; + } + for(var i=0; i}. The control that initialized this handler. The + * control is assumed to have a valid map property - that map is used + * in the handler's own setMap method. + */ + control: null, + + /** + * Property: map + * {} + */ + map: null, + + /** + * APIProperty: keyMask + * {Integer} Use bitwise operators and one or more of the OpenLayers.Handler + * constants to construct a keyMask. The keyMask is used by + * . If the keyMask matches the combination of keys + * down on an event, checkModifiers returns true. + * + * Example: + * (code) + * // handler only responds if the Shift key is down + * handler.keyMask = OpenLayers.Handler.MOD_SHIFT; + * + * // handler only responds if Ctrl-Shift is down + * handler.keyMask = OpenLayers.Handler.MOD_SHIFT | + * OpenLayers.Handler.MOD_CTRL; + * (end) + */ + keyMask: null, + + /** + * Property: active + * {Boolean} + */ + active: false, + + /** + * Property: evt + * {Event} This property references the last event handled by the handler. + * Note that this property is not part of the stable API. Use of the + * evt property should be restricted to controls in the library + * or other applications that are willing to update with changes to + * the OpenLayers code. + */ + evt: null, + + /** + * Property: touch + * {Boolean} Indicates the support of touch events. When touch events are + * started touch will be true and all mouse related listeners will do + * nothing. + */ + touch: false, + + /** + * Constructor: OpenLayers.Handler + * Construct a handler. + * + * Parameters: + * control - {} The control that initialized this + * handler. The control is assumed to have a valid map property; that + * map is used in the handler's own setMap method. If a map property + * is present in the options argument it will be used instead. + * callbacks - {Object} An object whose properties correspond to abstracted + * events or sequences of browser events. The values for these + * properties are functions defined by the control that get called by + * the handler. + * options - {Object} An optional object whose properties will be set on + * the handler. + */ + initialize: function(control, callbacks, options) { + OpenLayers.Util.extend(this, options); + this.control = control; + this.callbacks = callbacks; + + var map = this.map || control.map; + if (map) { + this.setMap(map); + } + + this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_"); + }, + + /** + * Method: setMap + */ + setMap: function (map) { + this.map = map; + }, + + /** + * Method: checkModifiers + * Check the keyMask on the handler. If no is set, this always + * returns true. If a is set and it matches the combination + * of keys down on an event, this returns true. + * + * Returns: + * {Boolean} The keyMask matches the keys down on an event. + */ + checkModifiers: function (evt) { + if(this.keyMask == null) { + return true; + } + /* calculate the keyboard modifier mask for this event */ + var keyModifiers = + (evt.shiftKey ? OpenLayers.Handler.MOD_SHIFT : 0) | + (evt.ctrlKey ? OpenLayers.Handler.MOD_CTRL : 0) | + (evt.altKey ? OpenLayers.Handler.MOD_ALT : 0) | + (evt.metaKey ? OpenLayers.Handler.MOD_META : 0); + + /* if it differs from the handler object's key mask, + bail out of the event handler */ + return (keyModifiers == this.keyMask); + }, + + /** + * APIMethod: activate + * Turn on the handler. Returns false if the handler was already active. + * + * Returns: + * {Boolean} The handler was activated. + */ + activate: function() { + if(this.active) { + return false; + } + // register for event handlers defined on this class. + var events = OpenLayers.Events.prototype.BROWSER_EVENTS; + for (var i=0, len=events.length; i will be + * true and all mouse related listeners will do nothing. + */ + startTouch: function() { + if (!this.touch) { + this.touch = true; + var events = [ + "mousedown", "mouseup", "mousemove", "click", "dblclick", + "mouseout" + ]; + for (var i=0, len=events.length; i, returns false if any key is down. + */ +OpenLayers.Handler.MOD_NONE = 0; + +/** + * Constant: OpenLayers.Handler.MOD_SHIFT + * If set as the , returns false if Shift is down. + */ +OpenLayers.Handler.MOD_SHIFT = 1; + +/** + * Constant: OpenLayers.Handler.MOD_CTRL + * If set as the , returns false if Ctrl is down. + */ +OpenLayers.Handler.MOD_CTRL = 2; + +/** + * Constant: OpenLayers.Handler.MOD_ALT + * If set as the , returns false if Alt is down. + */ +OpenLayers.Handler.MOD_ALT = 4; + +/** + * Constant: OpenLayers.Handler.MOD_META + * If set as the , returns false if Cmd is down. + */ +OpenLayers.Handler.MOD_META = 8; + + +/* ====================================================================== + OpenLayers/Handler/Drag.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Handler.js + */ + +/** + * Class: OpenLayers.Handler.Drag + * The drag handler is used to deal with sequences of browser events related + * to dragging. The handler is used by controls that want to know when + * a drag sequence begins, when a drag is happening, and when it has + * finished. + * + * Controls that use the drag handler typically construct it with callbacks + * for 'down', 'move', and 'done'. Callbacks for these keys are called + * when the drag begins, with each move, and when the drag is done. In + * addition, controls can have callbacks keyed to 'up' and 'out' if they + * care to differentiate between the types of events that correspond with + * the end of a drag sequence. If no drag actually occurs (no mouse move) + * the 'down' and 'up' callbacks will be called, but not the 'done' + * callback. + * + * Create a new drag handler with the constructor. + * + * Inherits from: + * - + */ +OpenLayers.Handler.Drag = OpenLayers.Class(OpenLayers.Handler, { + + /** + * Property: started + * {Boolean} When a mousedown or touchstart event is received, we want to + * record it, but not set 'dragging' until the mouse moves after starting. + */ + started: false, + + /** + * Property: stopDown + * {Boolean} Stop propagation of mousedown events from getting to listeners + * on the same element. Default is true. + */ + stopDown: true, + + /** + * Property: dragging + * {Boolean} + */ + dragging: false, + + /** + * Property: last + * {} The last pixel location of the drag. + */ + last: null, + + /** + * Property: start + * {} The first pixel location of the drag. + */ + start: null, + + /** + * Property: lastMoveEvt + * {Object} The last mousemove event that occurred. Used to + * position the map correctly when our "delay drag" + * timeout expired. + */ + lastMoveEvt: null, + + /** + * Property: oldOnselectstart + * {Function} + */ + oldOnselectstart: null, + + /** + * Property: interval + * {Integer} In order to increase performance, an interval (in + * milliseconds) can be set to reduce the number of drag events + * called. If set, a new drag event will not be set until the + * interval has passed. + * Defaults to 0, meaning no interval. + */ + interval: 0, + + /** + * Property: timeoutId + * {String} The id of the timeout used for the mousedown interval. + * This is "private", and should be left alone. + */ + timeoutId: null, + + /** + * APIProperty: documentDrag + * {Boolean} If set to true, the handler will also handle mouse moves when + * the cursor has moved out of the map viewport. Default is false. + */ + documentDrag: false, + + /** + * Property: documentEvents + * {Boolean} Are we currently observing document events? + */ + documentEvents: null, + + /** + * Constructor: OpenLayers.Handler.Drag + * Returns OpenLayers.Handler.Drag + * + * Parameters: + * control - {} The control that is making use of + * this handler. If a handler is being used without a control, the + * handlers setMap method must be overridden to deal properly with + * the map. + * callbacks - {Object} An object containing a single function to be + * called when the drag operation is finished. The callback should + * expect to recieve a single argument, the pixel location of the event. + * Callbacks for 'move' and 'done' are supported. You can also speficy + * callbacks for 'down', 'up', and 'out' to respond to those events. + * options - {Object} + */ + initialize: function(control, callbacks, options) { + OpenLayers.Handler.prototype.initialize.apply(this, arguments); + + if (this.documentDrag === true) { + var me = this; + this._docMove = function(evt) { + me.mousemove({ + xy: {x: evt.clientX, y: evt.clientY}, + element: document + }); + }; + this._docUp = function(evt) { + me.mouseup({xy: {x: evt.clientX, y: evt.clientY}}); + }; + } + }, + + + /** + * Method: dragstart + * This private method is factorized from mousedown and touchstart methods + * + * Parameters: + * evt - {Event} The event + * + * Returns: + * {Boolean} Let the event propagate. + */ + dragstart: function (evt) { + var propagate = true; + this.dragging = false; + if (this.checkModifiers(evt) && + (OpenLayers.Event.isLeftClick(evt) || + OpenLayers.Event.isSingleTouch(evt))) { + this.started = true; + this.start = evt.xy; + this.last = evt.xy; + OpenLayers.Element.addClass( + this.map.viewPortDiv, "olDragDown" + ); + this.down(evt); + this.callback("down", [evt.xy]); + + // prevent document dragging + OpenLayers.Event.preventDefault(evt); + + if(!this.oldOnselectstart) { + this.oldOnselectstart = document.onselectstart ? + document.onselectstart : OpenLayers.Function.True; + } + document.onselectstart = OpenLayers.Function.False; + + propagate = !this.stopDown; + } else { + this.started = false; + this.start = null; + this.last = null; + } + return propagate; + }, + + /** + * Method: dragmove + * This private method is factorized from mousemove and touchmove methods + * + * Parameters: + * evt - {Event} The event + * + * Returns: + * {Boolean} Let the event propagate. + */ + dragmove: function (evt) { + this.lastMoveEvt = evt; + if (this.started && !this.timeoutId && (evt.xy.x != this.last.x || + evt.xy.y != this.last.y)) { + if(this.documentDrag === true && this.documentEvents) { + if(evt.element === document) { + this.adjustXY(evt); + // do setEvent manually because the documentEvents are not + // registered with the map + this.setEvent(evt); + } else { + this.removeDocumentEvents(); + } + } + if (this.interval > 0) { + this.timeoutId = setTimeout( + OpenLayers.Function.bind(this.removeTimeout, this), + this.interval); + } + this.dragging = true; + + this.move(evt); + this.callback("move", [evt.xy]); + if(!this.oldOnselectstart) { + this.oldOnselectstart = document.onselectstart; + document.onselectstart = OpenLayers.Function.False; + } + this.last = evt.xy; + } + return true; + }, + + /** + * Method: dragend + * This private method is factorized from mouseup and touchend methods + * + * Parameters: + * evt - {Event} The event + * + * Returns: + * {Boolean} Let the event propagate. + */ + dragend: function (evt) { + if (this.started) { + if(this.documentDrag === true && this.documentEvents) { + this.adjustXY(evt); + this.removeDocumentEvents(); + } + var dragged = (this.start != this.last); + this.started = false; + this.dragging = false; + OpenLayers.Element.removeClass( + this.map.viewPortDiv, "olDragDown" + ); + this.up(evt); + this.callback("up", [evt.xy]); + if(dragged) { + this.callback("done", [evt.xy]); + } + document.onselectstart = this.oldOnselectstart; + } + return true; + }, + + /** + * The four methods below (down, move, up, and out) are used by subclasses + * to do their own processing related to these mouse events. + */ + + /** + * Method: down + * This method is called during the handling of the mouse down event. + * Subclasses can do their own processing here. + * + * Parameters: + * evt - {Event} The mouse down event + */ + down: function(evt) { + }, + + /** + * Method: move + * This method is called during the handling of the mouse move event. + * Subclasses can do their own processing here. + * + * Parameters: + * evt - {Event} The mouse move event + * + */ + move: function(evt) { + }, + + /** + * Method: up + * This method is called during the handling of the mouse up event. + * Subclasses can do their own processing here. + * + * Parameters: + * evt - {Event} The mouse up event + */ + up: function(evt) { + }, + + /** + * Method: out + * This method is called during the handling of the mouse out event. + * Subclasses can do their own processing here. + * + * Parameters: + * evt - {Event} The mouse out event + */ + out: function(evt) { + }, + + /** + * The methods below are part of the magic of event handling. Because + * they are named like browser events, they are registered as listeners + * for the events they represent. + */ + + /** + * Method: mousedown + * Handle mousedown events + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} Let the event propagate. + */ + mousedown: function(evt) { + return this.dragstart(evt); + }, + + /** + * Method: touchstart + * Handle touchstart events + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} Let the event propagate. + */ + touchstart: function(evt) { + this.startTouch(); + return this.dragstart(evt); + }, + + /** + * Method: mousemove + * Handle mousemove events + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} Let the event propagate. + */ + mousemove: function(evt) { + return this.dragmove(evt); + }, + + /** + * Method: touchmove + * Handle touchmove events + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} Let the event propagate. + */ + touchmove: function(evt) { + return this.dragmove(evt); + }, + + /** + * Method: removeTimeout + * Private. Called by mousemove() to remove the drag timeout. + */ + removeTimeout: function() { + this.timeoutId = null; + // if timeout expires while we're still dragging (mouseup + // hasn't occurred) then call mousemove to move to the + // correct position + if(this.dragging) { + this.mousemove(this.lastMoveEvt); + } + }, + + /** + * Method: mouseup + * Handle mouseup events + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} Let the event propagate. + */ + mouseup: function(evt) { + return this.dragend(evt); + }, + + /** + * Method: touchend + * Handle touchend events + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} Let the event propagate. + */ + touchend: function(evt) { + // override evt.xy with last position since touchend does not have + // any touch position + evt.xy = this.last; + return this.dragend(evt); + }, + + /** + * Method: mouseout + * Handle mouseout events + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} Let the event propagate. + */ + mouseout: function (evt) { + if (this.started && OpenLayers.Util.mouseLeft(evt, this.map.viewPortDiv)) { + if(this.documentDrag === true) { + this.addDocumentEvents(); + } else { + var dragged = (this.start != this.last); + this.started = false; + this.dragging = false; + OpenLayers.Element.removeClass( + this.map.viewPortDiv, "olDragDown" + ); + this.out(evt); + this.callback("out", []); + if(dragged) { + this.callback("done", [evt.xy]); + } + if(document.onselectstart) { + document.onselectstart = this.oldOnselectstart; + } + } + } + return true; + }, + + /** + * Method: click + * The drag handler captures the click event. If something else registers + * for clicks on the same element, its listener will not be called + * after a drag. + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} Let the event propagate. + */ + click: function (evt) { + // let the click event propagate only if the mouse moved + return (this.start == this.last); + }, + + /** + * Method: activate + * Activate the handler. + * + * Returns: + * {Boolean} The handler was successfully activated. + */ + activate: function() { + var activated = false; + if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) { + this.dragging = false; + activated = true; + } + return activated; + }, + + /** + * Method: deactivate + * Deactivate the handler. + * + * Returns: + * {Boolean} The handler was successfully deactivated. + */ + deactivate: function() { + var deactivated = false; + if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) { + this.started = false; + this.dragging = false; + this.start = null; + this.last = null; + deactivated = true; + OpenLayers.Element.removeClass( + this.map.viewPortDiv, "olDragDown" + ); + } + return deactivated; + }, + + /** + * Method: adjustXY + * Converts event coordinates that are relative to the document body to + * ones that are relative to the map viewport. The latter is the default in + * OpenLayers. + * + * Parameters: + * evt - {Object} + */ + adjustXY: function(evt) { + var pos = OpenLayers.Util.pagePosition(this.map.viewPortDiv); + evt.xy.x -= pos[0]; + evt.xy.y -= pos[1]; + }, + + /** + * Method: addDocumentEvents + * Start observing document events when documentDrag is true and the mouse + * cursor leaves the map viewport while dragging. + */ + addDocumentEvents: function() { + OpenLayers.Element.addClass(document.body, "olDragDown"); + this.documentEvents = true; + OpenLayers.Event.observe(document, "mousemove", this._docMove); + OpenLayers.Event.observe(document, "mouseup", this._docUp); + }, + + /** + * Method: removeDocumentEvents + * Stops observing document events when documentDrag is true and the mouse + * cursor re-enters the map viewport while dragging. + */ + removeDocumentEvents: function() { + OpenLayers.Element.removeClass(document.body, "olDragDown"); + this.documentEvents = false; + OpenLayers.Event.stopObserving(document, "mousemove", this._docMove); + OpenLayers.Event.stopObserving(document, "mouseup", this._docUp); + }, + + CLASS_NAME: "OpenLayers.Handler.Drag" +}); +/* ====================================================================== + OpenLayers/Handler/Keyboard.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Handler.js + * @requires OpenLayers/Events.js + */ + +/** + * Class: OpenLayers.handler.Keyboard + * A handler for keyboard events. Create a new instance with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Handler.Keyboard = OpenLayers.Class(OpenLayers.Handler, { + + /* http://www.quirksmode.org/js/keys.html explains key x-browser + key handling quirks in pretty nice detail */ + + /** + * Constant: KEY_EVENTS + * keydown, keypress, keyup + */ + KEY_EVENTS: ["keydown", "keyup"], + + /** + * Property: eventListener + * {Function} + */ + eventListener: null, + + /** + * Property: observeElement + * {DOMElement|String} The DOM element on which we listen for + * key events. Default to the document. + */ + observeElement: null, + + /** + * Constructor: OpenLayers.Handler.Keyboard + * Returns a new keyboard handler. + * + * Parameters: + * control - {} The control that is making use of + * this handler. If a handler is being used without a control, the + * handlers setMap method must be overridden to deal properly with + * the map. + * callbacks - {Object} An object containing a single function to be + * called when the drag operation is finished. The callback should + * expect to recieve a single argument, the pixel location of the event. + * Callbacks for 'keydown', 'keypress', and 'keyup' are supported. + * options - {Object} Optional object whose properties will be set on the + * handler. + */ + initialize: function(control, callbacks, options) { + OpenLayers.Handler.prototype.initialize.apply(this, arguments); + // cache the bound event listener method so it can be unobserved later + this.eventListener = OpenLayers.Function.bindAsEventListener( + this.handleKeyEvent, this + ); + }, + + /** + * Method: destroy + */ + destroy: function() { + this.deactivate(); + this.eventListener = null; + OpenLayers.Handler.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: activate + */ + activate: function() { + if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) { + this.observeElement = this.observeElement || document; + for (var i=0, len=this.KEY_EVENTS.length; i constructor. + * + * Inherits From: + * - + */ +OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, { + + /** + * APIProperty: documentDrag + * {Boolean} If set to true, dragging vertices will continue even if the + * mouse cursor leaves the map viewport. Default is false. + */ + documentDrag: false, + + /** + * APIProperty: geometryTypes + * {Array(String)} To restrict modification to a limited set of geometry + * types, send a list of strings corresponding to the geometry class + * names. + */ + geometryTypes: null, + + /** + * APIProperty: clickout + * {Boolean} Unselect features when clicking outside any feature. + * Default is true. + */ + clickout: true, + + /** + * APIProperty: toggle + * {Boolean} Unselect a selected feature on click. + * Default is true. + */ + toggle: true, + + /** + * APIProperty: standalone + * {Boolean} Set to true to create a control without SelectFeature + * capabilities. Default is false. If standalone is true, to modify + * a feature, call the method with the target feature. + * Note that you must call the method to finish + * feature modification in standalone mode (before starting to modify + * another feature). + */ + standalone: false, + + /** + * Property: layer + * {} + */ + layer: null, + + /** + * Property: feature + * {} Feature currently available for modification. + */ + feature: null, + + /** + * Property: vertex + * {} Vertex currently being modified. + */ + vertex: null, + + /** + * Property: vertices + * {Array()} Verticies currently available + * for dragging. + */ + vertices: null, + + /** + * Property: virtualVertices + * {Array()} Virtual vertices in the middle + * of each edge. + */ + virtualVertices: null, + + /** + * Property: handlers + * {Object} + */ + handlers: null, + + /** + * APIProperty: deleteCodes + * {Array(Integer)} Keycodes for deleting verticies. Set to null to disable + * vertex deltion by keypress. If non-null, keypresses with codes + * in this array will delete vertices under the mouse. Default + * is 46 and 68, the 'delete' and lowercase 'd' keys. + */ + deleteCodes: null, + + /** + * APIProperty: virtualStyle + * {Object} A symbolizer to be used for virtual vertices. + */ + virtualStyle: null, + + /** + * APIProperty: vertexRenderIntent + * {String} The renderIntent to use for vertices. If no is + * provided, this renderIntent will also be used for virtual vertices, with + * a fillOpacity and strokeOpacity of 0.3. Default is null, which means + * that the layer's default style will be used for vertices. + */ + vertexRenderIntent: null, + + /** + * APIProperty: mode + * {Integer} Bitfields specifying the modification mode. Defaults to + * OpenLayers.Control.ModifyFeature.RESHAPE. To set the mode to a + * combination of options, use the | operator. For example, to allow + * the control to both resize and rotate features, use the following + * syntax + * (code) + * control.mode = OpenLayers.Control.ModifyFeature.RESIZE | + * OpenLayers.Control.ModifyFeature.ROTATE; + * (end) + */ + mode: null, + + /** + * APIProperty: createVertices + * {Boolean} Create new vertices by dragging the virtual vertices + * in the middle of each edge. Default is true. + */ + createVertices: true, + + /** + * Property: modified + * {Boolean} The currently selected feature has been modified. + */ + modified: false, + + /** + * Property: radiusHandle + * {} A handle for rotating/resizing a feature. + */ + radiusHandle: null, + + /** + * Property: dragHandle + * {} A handle for dragging a feature. + */ + dragHandle: null, + + /** + * APIProperty: onModificationStart + * {Function} *Deprecated*. Register for "beforefeaturemodified" instead. + * The "beforefeaturemodified" event is triggered on the layer before + * any modification begins. + * + * Optional function to be called when a feature is selected + * to be modified. The function should expect to be called with a + * feature. This could be used for example to allow to lock the + * feature on server-side. + */ + onModificationStart: function() {}, + + /** + * APIProperty: onModification + * {Function} *Deprecated*. Register for "featuremodified" instead. + * The "featuremodified" event is triggered on the layer with each + * feature modification. + * + * Optional function to be called when a feature has been + * modified. The function should expect to be called with a feature. + */ + onModification: function() {}, + + /** + * APIProperty: onModificationEnd + * {Function} *Deprecated*. Register for "afterfeaturemodified" instead. + * The "afterfeaturemodified" event is triggered on the layer after + * a feature has been modified. + * + * Optional function to be called when a feature is finished + * being modified. The function should expect to be called with a + * feature. + */ + onModificationEnd: function() {}, + + /** + * Constructor: OpenLayers.Control.ModifyFeature + * Create a new modify feature control. + * + * Parameters: + * layer - {} Layer that contains features that + * will be modified. + * options - {Object} Optional object whose properties will be set on the + * control. + */ + initialize: function(layer, options) { + options = options || {}; + this.layer = layer; + this.vertices = []; + this.virtualVertices = []; + this.virtualStyle = OpenLayers.Util.extend({}, + this.layer.style || + this.layer.styleMap.createSymbolizer(null, options.vertexRenderIntent) + ); + this.virtualStyle.fillOpacity = 0.3; + this.virtualStyle.strokeOpacity = 0.3; + this.deleteCodes = [46, 68]; + this.mode = OpenLayers.Control.ModifyFeature.RESHAPE; + OpenLayers.Control.prototype.initialize.apply(this, [options]); + if(!(OpenLayers.Util.isArray(this.deleteCodes))) { + this.deleteCodes = [this.deleteCodes]; + } + + // configure the drag handler + var dragCallbacks = { + down: function(pixel) { + this.vertex = null; + var feature = this.layer.getFeatureFromEvent( + this.handlers.drag.evt); + if (feature) { + this.dragStart(feature); + } else if (this.clickout) { + this._unselect = this.feature; + } + }, + move: function(pixel) { + delete this._unselect; + if (this.vertex) { + this.dragVertex(this.vertex, pixel); + } + }, + up: function() { + this.handlers.drag.stopDown = false; + if (this._unselect) { + this.unselectFeature(this._unselect); + delete this._unselect; + } + }, + done: function(pixel) { + if (this.vertex) { + this.dragComplete(this.vertex); + } + } + }; + var dragOptions = { + documentDrag: this.documentDrag, + stopDown: false + }; + + // configure the keyboard handler + var keyboardOptions = { + keydown: this.handleKeypress + }; + this.handlers = { + keyboard: new OpenLayers.Handler.Keyboard(this, keyboardOptions), + drag: new OpenLayers.Handler.Drag(this, dragCallbacks, dragOptions) + }; + }, + + /** + * APIMethod: destroy + * Take care of things that are not handled in superclass. + */ + destroy: function() { + if (this.map) { + this.map.events.un({ + "removelayer": this.handleMapEvents, + "changelayer": this.handleMapEvents, + scope: this + }); + } + this.layer = null; + OpenLayers.Control.prototype.destroy.apply(this, []); + }, + + /** + * APIMethod: activate + * Activate the control. + * + * Returns: + * {Boolean} Successfully activated the control. + */ + activate: function() { + this.moveLayerToTop(); + this.map.events.on({ + "removelayer": this.handleMapEvents, + "changelayer": this.handleMapEvents, + scope: this + }); + return (this.handlers.keyboard.activate() && + this.handlers.drag.activate() && + OpenLayers.Control.prototype.activate.apply(this, arguments)); + }, + + /** + * APIMethod: deactivate + * Deactivate the control. + * + * Returns: + * {Boolean} Successfully deactivated the control. + */ + deactivate: function() { + var deactivated = false; + // the return from the controls is unimportant in this case + if(OpenLayers.Control.prototype.deactivate.apply(this, arguments)) { + this.moveLayerBack(); + this.map.events.un({ + "removelayer": this.handleMapEvents, + "changelayer": this.handleMapEvents, + scope: this + }); + this.layer.removeFeatures(this.vertices, {silent: true}); + this.layer.removeFeatures(this.virtualVertices, {silent: true}); + this.vertices = []; + this.handlers.drag.deactivate(); + this.handlers.keyboard.deactivate(); + var feature = this.feature; + if (feature && feature.geometry && feature.layer) { + this.unselectFeature(feature); + } + deactivated = true; + } + return deactivated; + }, + + /** + * Method: beforeSelectFeature + * Called before a feature is selected. + * + * Parameters: + * feature - {} The feature about to be selected. + */ + beforeSelectFeature: function(feature) { + return this.layer.events.triggerEvent( + "beforefeaturemodified", {feature: feature} + ); + }, + + /** + * APIMethod: selectFeature + * Select a feature for modification in standalone mode. In non-standalone + * mode, this method is called when a feature is selected by clicking. + * Register a listener to the beforefeaturemodified event and return false + * to prevent feature modification. + * + * Parameters: + * feature - {} the selected feature. + */ + selectFeature: function(feature) { + if (this.feature === feature || + (this.geometryTypes && OpenLayers.Util.indexOf(this.geometryTypes, + feature.geometry.CLASS_NAME) == -1)) { + return; + } + if (this.beforeSelectFeature(feature) !== false) { + if (this.feature) { + this.unselectFeature(this.feature); + } + this.feature = feature; + this.layer.selectedFeatures.push(feature); + this.layer.drawFeature(feature, 'select'); + this.modified = false; + this.resetVertices(); + this.onModificationStart(this.feature); + } + // keep track of geometry modifications + var modified = feature.modified; + if (feature.geometry && !(modified && modified.geometry)) { + this._originalGeometry = feature.geometry.clone(); + } + }, + + /** + * APIMethod: unselectFeature + * Called when the select feature control unselects a feature. + * + * Parameters: + * feature - {} The unselected feature. + */ + unselectFeature: function(feature) { + this.layer.removeFeatures(this.vertices, {silent: true}); + this.vertices = []; + this.layer.destroyFeatures(this.virtualVertices, {silent: true}); + this.virtualVertices = []; + if(this.dragHandle) { + this.layer.destroyFeatures([this.dragHandle], {silent: true}); + delete this.dragHandle; + } + if(this.radiusHandle) { + this.layer.destroyFeatures([this.radiusHandle], {silent: true}); + delete this.radiusHandle; + } + this.layer.drawFeature(this.feature, 'default'); + this.feature = null; + OpenLayers.Util.removeItem(this.layer.selectedFeatures, feature); + this.onModificationEnd(feature); + this.layer.events.triggerEvent("afterfeaturemodified", { + feature: feature, + modified: this.modified + }); + this.modified = false; + }, + + + /** + * Method: dragStart + * Called by the drag handler before a feature is dragged. This method is + * used to differentiate between points and vertices + * of higher order geometries. + * + * Parameters: + * feature - {} The point or vertex about to be + * dragged. + */ + dragStart: function(feature) { + var isPoint = feature.geometry.CLASS_NAME == + 'OpenLayers.Geometry.Point'; + if (!this.standalone && + ((!feature._sketch && isPoint) || !feature._sketch)) { + if (this.toggle && this.feature === feature) { + // mark feature for unselection + this._unselect = feature; + } + this.selectFeature(feature); + } + if (feature._sketch || isPoint) { + // feature is a drag or virtual handle or point + this.vertex = feature; + this.handlers.drag.stopDown = true; + } + }, + + /** + * Method: dragVertex + * Called by the drag handler with each drag move of a vertex. + * + * Parameters: + * vertex - {} The vertex being dragged. + * pixel - {} Pixel location of the mouse event. + */ + dragVertex: function(vertex, pixel) { + var pos = this.map.getLonLatFromViewPortPx(pixel); + var geom = vertex.geometry; + geom.move(pos.lon - geom.x, pos.lat - geom.y); + this.modified = true; + /** + * Five cases: + * 1) dragging a simple point + * 2) dragging a virtual vertex + * 3) dragging a drag handle + * 4) dragging a real vertex + * 5) dragging a radius handle + */ + if(this.feature.geometry.CLASS_NAME == "OpenLayers.Geometry.Point") { + // dragging a simple point + this.layer.events.triggerEvent("vertexmodified", { + vertex: vertex.geometry, + feature: this.feature, + pixel: pixel + }); + } else { + if(vertex._index) { + // dragging a virtual vertex + vertex.geometry.parent.addComponent(vertex.geometry, + vertex._index); + // move from virtual to real vertex + delete vertex._index; + OpenLayers.Util.removeItem(this.virtualVertices, vertex); + this.vertices.push(vertex); + } else if(vertex == this.dragHandle) { + // dragging a drag handle + this.layer.removeFeatures(this.vertices, {silent: true}); + this.vertices = []; + if(this.radiusHandle) { + this.layer.destroyFeatures([this.radiusHandle], {silent: true}); + this.radiusHandle = null; + } + } else if(vertex !== this.radiusHandle) { + // dragging a real vertex + this.layer.events.triggerEvent("vertexmodified", { + vertex: vertex.geometry, + feature: this.feature, + pixel: pixel + }); + } + // dragging a radius handle - no special treatment + if(this.virtualVertices.length > 0) { + this.layer.destroyFeatures(this.virtualVertices, {silent: true}); + this.virtualVertices = []; + } + this.layer.drawFeature(this.feature, this.standalone ? undefined : + 'select'); + } + // keep the vertex on top so it gets the mouseout after dragging + // this should be removed in favor of an option to draw under or + // maintain node z-index + this.layer.drawFeature(vertex); + }, + + /** + * Method: dragComplete + * Called by the drag handler when the feature dragging is complete. + * + * Parameters: + * vertex - {} The vertex being dragged. + */ + dragComplete: function(vertex) { + this.resetVertices(); + this.setFeatureState(); + this.onModification(this.feature); + this.layer.events.triggerEvent("featuremodified", + {feature: this.feature}); + }, + + /** + * Method: setFeatureState + * Called when the feature is modified. If the current state is not + * INSERT or DELETE, the state is set to UPDATE. + */ + setFeatureState: function() { + if(this.feature.state != OpenLayers.State.INSERT && + this.feature.state != OpenLayers.State.DELETE) { + this.feature.state = OpenLayers.State.UPDATE; + if (this.modified && this._originalGeometry) { + var feature = this.feature; + feature.modified = OpenLayers.Util.extend(feature.modified, { + geometry: this._originalGeometry + }); + delete this._originalGeometry; + } + } + }, + + /** + * Method: resetVertices + */ + resetVertices: function() { + if(this.vertices.length > 0) { + this.layer.removeFeatures(this.vertices, {silent: true}); + this.vertices = []; + } + if(this.virtualVertices.length > 0) { + this.layer.removeFeatures(this.virtualVertices, {silent: true}); + this.virtualVertices = []; + } + if(this.dragHandle) { + this.layer.destroyFeatures([this.dragHandle], {silent: true}); + this.dragHandle = null; + } + if(this.radiusHandle) { + this.layer.destroyFeatures([this.radiusHandle], {silent: true}); + this.radiusHandle = null; + } + if(this.feature && + this.feature.geometry.CLASS_NAME != "OpenLayers.Geometry.Point") { + if((this.mode & OpenLayers.Control.ModifyFeature.DRAG)) { + this.collectDragHandle(); + } + if((this.mode & (OpenLayers.Control.ModifyFeature.ROTATE | + OpenLayers.Control.ModifyFeature.RESIZE))) { + this.collectRadiusHandle(); + } + if(this.mode & OpenLayers.Control.ModifyFeature.RESHAPE){ + // Don't collect vertices when we're resizing + if (!(this.mode & OpenLayers.Control.ModifyFeature.RESIZE)){ + this.collectVertices(); + } + } + } + }, + + /** + * Method: handleKeypress + * Called by the feature handler on keypress. This is used to delete + * vertices. If the property is set, vertices will + * be deleted when a feature is selected for modification and + * the mouse is over a vertex. + * + * Parameters: + * evt - {Event} Keypress event. + */ + handleKeypress: function(evt) { + var code = evt.keyCode; + + // check for delete key + if(this.feature && + OpenLayers.Util.indexOf(this.deleteCodes, code) != -1) { + var vertex = this.layer.getFeatureFromEvent(this.handlers.drag.evt); + if (vertex && + OpenLayers.Util.indexOf(this.vertices, vertex) != -1 && + !this.handlers.drag.dragging && vertex.geometry.parent) { + // remove the vertex + vertex.geometry.parent.removeComponent(vertex.geometry); + this.layer.events.triggerEvent("vertexremoved", { + vertex: vertex.geometry, + feature: this.feature, + pixel: evt.xy + }); + this.layer.drawFeature(this.feature, this.standalone ? + undefined : 'select'); + this.modified = true; + this.resetVertices(); + this.setFeatureState(); + this.onModification(this.feature); + this.layer.events.triggerEvent("featuremodified", + {feature: this.feature}); + } + } + }, + + /** + * Method: collectVertices + * Collect the vertices from the modifiable feature's geometry and push + * them on to the control's vertices array. + */ + collectVertices: function() { + this.vertices = []; + this.virtualVertices = []; + var control = this; + function collectComponentVertices(geometry) { + var i, vertex, component, len; + if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") { + vertex = new OpenLayers.Feature.Vector(geometry); + vertex._sketch = true; + vertex.renderIntent = control.vertexRenderIntent; + control.vertices.push(vertex); + } else { + var numVert = geometry.components.length; + if(geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") { + numVert -= 1; + } + for(i=0; i} The control's map. + */ + setMap: function(map) { + this.handlers.drag.setMap(map); + OpenLayers.Control.prototype.setMap.apply(this, arguments); + }, + + /** + * Method: handleMapEvents + * + * Parameters: + * evt - {Object} + */ + handleMapEvents: function(evt) { + if (evt.type == "removelayer" || evt.property == "order") { + this.moveLayerToTop(); + } + }, + + /** + * Method: moveLayerToTop + * Moves the layer for this handler to the top, so mouse events can reach + * it. + */ + moveLayerToTop: function() { + var index = Math.max(this.map.Z_INDEX_BASE['Feature'] - 1, + this.layer.getZIndex()) + 1; + this.layer.setZIndex(index); + + }, + + /** + * Method: moveLayerBack + * Moves the layer back to the position determined by the map's layers + * array. + */ + moveLayerBack: function() { + var index = this.layer.getZIndex() - 1; + if (index >= this.map.Z_INDEX_BASE['Feature']) { + this.layer.setZIndex(index); + } else { + this.map.setLayerZIndex(this.layer, + this.map.getLayerIndex(this.layer)); + } + }, + + CLASS_NAME: "OpenLayers.Control.ModifyFeature" +}); + +/** + * Constant: RESHAPE + * {Integer} Constant used to make the control work in reshape mode + */ +OpenLayers.Control.ModifyFeature.RESHAPE = 1; +/** + * Constant: RESIZE + * {Integer} Constant used to make the control work in resize mode + */ +OpenLayers.Control.ModifyFeature.RESIZE = 2; +/** + * Constant: ROTATE + * {Integer} Constant used to make the control work in rotate mode + */ +OpenLayers.Control.ModifyFeature.ROTATE = 4; +/** + * Constant: DRAG + * {Integer} Constant used to make the control work in drag mode + */ +OpenLayers.Control.ModifyFeature.DRAG = 8; +/* ====================================================================== + OpenLayers/Layer/Bing.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Layer/XYZ.js + */ + +/** + * Class: OpenLayers.Layer.Bing + * Bing layer using direct tile access as provided by Bing Maps REST Services. + * See http://msdn.microsoft.com/en-us/library/ff701713.aspx for more + * information. Note: Terms of Service compliant use requires the map to be + * configured with an control and the + * attribution placed on or near the map. + * + * Inherits from: + * - + */ +OpenLayers.Layer.Bing = OpenLayers.Class(OpenLayers.Layer.XYZ, { + + /** + * Property: key + * {String} API key for Bing maps, get your own key + * at http://bingmapsportal.com/ . + */ + key: null, + + /** + * Property: serverResolutions + * {Array} the resolutions provided by the Bing servers. + */ + serverResolutions: [ + 156543.03390625, 78271.516953125, 39135.7584765625, + 19567.87923828125, 9783.939619140625, 4891.9698095703125, + 2445.9849047851562, 1222.9924523925781, 611.4962261962891, + 305.74811309814453, 152.87405654907226, 76.43702827453613, + 38.218514137268066, 19.109257068634033, 9.554628534317017, + 4.777314267158508, 2.388657133579254, 1.194328566789627, + 0.5971642833948135, 0.29858214169740677, 0.14929107084870338, + 0.07464553542435169 + ], + + /** + * Property: attributionTemplate + * {String} + */ + attributionTemplate: '' + + '${copyrights}' + + '' + + 'Terms of Use', + + /** + * Property: metadata + * {Object} Metadata for this layer, as returned by the callback script + */ + metadata: null, + + /** + * Property: protocolRegex + * {RegExp} Regular expression to match and replace http: in bing urls + */ + protocolRegex: /^http:/i, + + /** + * APIProperty: type + * {String} The layer identifier. Any non-birdseye imageryType + * from http://msdn.microsoft.com/en-us/library/ff701716.aspx can be + * used. Default is "Road". + */ + type: "Road", + + /** + * APIProperty: culture + * {String} The culture identifier. See http://msdn.microsoft.com/en-us/library/ff701709.aspx + * for the definition and the possible values. Default is "en-US". + */ + culture: "en-US", + + /** + * APIProperty: metadataParams + * {Object} Optional url parameters for the Get Imagery Metadata request + * as described here: http://msdn.microsoft.com/en-us/library/ff701716.aspx + */ + metadataParams: null, + + /** APIProperty: tileOptions + * {Object} optional configuration options for instances + * created by this Layer. Default is + * + * (code) + * {crossOriginKeyword: 'anonymous'} + * (end) + */ + tileOptions: null, + + /** APIProperty: protocol + * {String} Protocol to use to fetch Imagery Metadata, tiles and bing logo + * Can be 'http:' 'https:' or '' + * + * Warning: tiles may not be available under both HTTP and HTTPS protocols. + * Microsoft approved use of both HTTP and HTTPS urls for tiles. However + * this is undocumented and the Imagery Metadata API always returns HTTP + * urls. + * + * Default is '', unless when executed from a file:/// uri, in which case + * it is 'http:'. + */ + protocol: ~window.location.href.indexOf('http') ? '' : 'http:', + + /** + * Constructor: OpenLayers.Layer.Bing + * Create a new Bing layer. + * + * Example: + * (code) + * var road = new OpenLayers.Layer.Bing({ + * name: "My Bing Aerial Layer", + * type: "Aerial", + * key: "my-api-key-here", + * }); + * (end) + * + * Parameters: + * options - {Object} Configuration properties for the layer. + * + * Required configuration properties: + * key - {String} Bing Maps API key for your application. Get one at + * http://bingmapsportal.com/. + * type - {String} The layer identifier. Any non-birdseye imageryType + * from http://msdn.microsoft.com/en-us/library/ff701716.aspx can be + * used. + * + * Any other documented layer properties can be provided in the config object. + */ + initialize: function(options) { + options = OpenLayers.Util.applyDefaults({ + sphericalMercator: true + }, options); + var name = options.name || "Bing " + (options.type || this.type); + + var newArgs = [name, null, options]; + OpenLayers.Layer.XYZ.prototype.initialize.apply(this, newArgs); + this.tileOptions = OpenLayers.Util.extend({ + crossOriginKeyword: 'anonymous' + }, this.options.tileOptions); + this.loadMetadata(); + }, + + /** + * Method: loadMetadata + */ + loadMetadata: function() { + this._callbackId = "_callback_" + this.id.replace(/\./g, "_"); + // link the processMetadata method to the global scope and bind it + // to this instance + window[this._callbackId] = OpenLayers.Function.bind( + OpenLayers.Layer.Bing.processMetadata, this + ); + var params = OpenLayers.Util.applyDefaults({ + key: this.key, + jsonp: this._callbackId, + include: "ImageryProviders" + }, this.metadataParams); + var url = this.protocol + "//dev.virtualearth.net/REST/v1/Imagery/Metadata/" + + this.type + "?" + OpenLayers.Util.getParameterString(params); + var script = document.createElement("script"); + script.type = "text/javascript"; + script.src = url; + script.id = this._callbackId; + document.getElementsByTagName("head")[0].appendChild(script); + }, + + /** + * Method: initLayer + * + * Sets layer properties according to the metadata provided by the API + */ + initLayer: function() { + var res = this.metadata.resourceSets[0].resources[0]; + var url = res.imageUrl.replace("{quadkey}", "${quadkey}"); + url = url.replace("{culture}", this.culture); + url = url.replace(this.protocolRegex, this.protocol); + this.url = []; + for (var i=0; i} + */ + getURL: function(bounds) { + if (!this.url) { + return; + } + var xyz = this.getXYZ(bounds), x = xyz.x, y = xyz.y, z = xyz.z; + var quadDigits = []; + for (var i = z; i > 0; --i) { + var digit = '0'; + var mask = 1 << (i - 1); + if ((x & mask) != 0) { + digit++; + } + if ((y & mask) != 0) { + digit++; + digit++; + } + quadDigits.push(digit); + } + var quadKey = quadDigits.join(""); + var url = this.selectUrl('' + x + y + z, this.url); + + return OpenLayers.String.format(url, {'quadkey': quadKey}); + }, + + /** + * Method: updateAttribution + * Updates the attribution according to the requirements outlined in + * http://gis.638310.n2.nabble.com/Bing-imagery-td5789168.html + */ + updateAttribution: function() { + var metadata = this.metadata; + if (!metadata.resourceSets || !this.map || !this.map.center) { + return; + } + var res = metadata.resourceSets[0].resources[0]; + var extent = this.map.getExtent().transform( + this.map.getProjectionObject(), + new OpenLayers.Projection("EPSG:4326") + ); + var providers = res.imageryProviders || [], + zoom = OpenLayers.Util.indexOf(this.serverResolutions, + this.getServerResolution()), + copyrights = "", provider, i, ii, j, jj, bbox, coverage; + for (i=0,ii=providers.length; i= coverage.zoomMin) { + copyrights += provider.attribution + " "; + } + } + } + var logo = metadata.brandLogoUri.replace(this.protocolRegex, this.protocol); + this.attribution = OpenLayers.String.format(this.attributionTemplate, { + type: this.type.toLowerCase(), + logo: logo, + copyrights: copyrights + }); + this.map && this.map.events.triggerEvent("changelayer", { + layer: this, + property: "attribution" + }); + }, + + /** + * Method: setMap + */ + setMap: function() { + OpenLayers.Layer.XYZ.prototype.setMap.apply(this, arguments); + this.map.events.register("moveend", this, this.updateAttribution); + }, + + /** + * APIMethod: clone + * + * Parameters: + * obj - {Object} + * + * Returns: + * {} An exact clone of this + */ + clone: function(obj) { + if (obj == null) { + obj = new OpenLayers.Layer.Bing(this.options); + } + //get all additions from superclasses + obj = OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]); + // copy/set any non-init, non-simple values here + return obj; + }, + + /** + * Method: destroy + */ + destroy: function() { + this.map && + this.map.events.unregister("moveend", this, this.updateAttribution); + OpenLayers.Layer.XYZ.prototype.destroy.apply(this, arguments); + }, + + CLASS_NAME: "OpenLayers.Layer.Bing" +}); + +/** + * Function: OpenLayers.Layer.Bing.processMetadata + * This function will be bound to an instance, linked to the global scope with + * an id, and called by the JSONP script returned by the API. + * + * Parameters: + * metadata - {Object} metadata as returned by the API + */ +OpenLayers.Layer.Bing.processMetadata = function(metadata) { + this.metadata = metadata; + this.initLayer(); + var script = document.getElementById(this._callbackId); + script.parentNode.removeChild(script); + window[this._callbackId] = undefined; // cannot delete from window in IE + delete this._callbackId; +}; +/* ====================================================================== + OpenLayers/Geometry/MultiLineString.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Geometry/Collection.js + * @requires OpenLayers/Geometry/LineString.js + */ + +/** + * Class: OpenLayers.Geometry.MultiLineString + * A MultiLineString is a geometry with multiple + * components. + * + * Inherits from: + * - + * - + */ +OpenLayers.Geometry.MultiLineString = OpenLayers.Class( + OpenLayers.Geometry.Collection, { + + /** + * Property: componentTypes + * {Array(String)} An array of class names representing the types of + * components that the collection can include. A null value means the + * component types are not restricted. + */ + componentTypes: ["OpenLayers.Geometry.LineString"], + + /** + * Constructor: OpenLayers.Geometry.MultiLineString + * Constructor for a MultiLineString Geometry. + * + * Parameters: + * components - {Array()} + * + */ + + /** + * Method: split + * Use this geometry (the source) to attempt to split a target geometry. + * + * Parameters: + * geometry - {} The target geometry. + * options - {Object} Properties of this object will be used to determine + * how the split is conducted. + * + * Valid options: + * mutual - {Boolean} Split the source geometry in addition to the target + * geometry. Default is false. + * edge - {Boolean} Allow splitting when only edges intersect. Default is + * true. If false, a vertex on the source must be within the tolerance + * distance of the intersection to be considered a split. + * tolerance - {Number} If a non-null value is provided, intersections + * within the tolerance distance of an existing vertex on the source + * will be assumed to occur at the vertex. + * + * Returns: + * {Array} A list of geometries (of this same type as the target) that + * result from splitting the target with the source geometry. The + * source and target geometry will remain unmodified. If no split + * results, null will be returned. If mutual is true and a split + * results, return will be an array of two arrays - the first will be + * all geometries that result from splitting the source geometry and + * the second will be all geometries that result from splitting the + * target geometry. + */ + split: function(geometry, options) { + var results = null; + var mutual = options && options.mutual; + var splits, sourceLine, sourceLines, sourceSplit, targetSplit; + var sourceParts = []; + var targetParts = [geometry]; + for(var i=0, len=this.components.length; i 1) { + sourceSplit = true; + } else { + sourceParts = []; + } + if(targetParts && targetParts.length > 1) { + targetSplit = true; + } else { + targetParts = []; + } + if(sourceSplit || targetSplit) { + if(mutual) { + results = [sourceParts, targetParts]; + } else { + results = targetParts; + } + } + return results; + }, + + /** + * Method: splitWith + * Split this geometry (the target) with the given geometry (the source). + * + * Parameters: + * geometry - {} A geometry used to split this + * geometry (the source). + * options - {Object} Properties of this object will be used to determine + * how the split is conducted. + * + * Valid options: + * mutual - {Boolean} Split the source geometry in addition to the target + * geometry. Default is false. + * edge - {Boolean} Allow splitting when only edges intersect. Default is + * true. If false, a vertex on the source must be within the tolerance + * distance of the intersection to be considered a split. + * tolerance - {Number} If a non-null value is provided, intersections + * within the tolerance distance of an existing vertex on the source + * will be assumed to occur at the vertex. + * + * Returns: + * {Array} A list of geometries (of this same type as the target) that + * result from splitting the target with the source geometry. The + * source and target geometry will remain unmodified. If no split + * results, null will be returned. If mutual is true and a split + * results, return will be an array of two arrays - the first will be + * all geometries that result from splitting the source geometry and + * the second will be all geometries that result from splitting the + * target geometry. + */ + splitWith: function(geometry, options) { + var results = null; + var mutual = options && options.mutual; + var splits, targetLine, sourceLines, sourceSplit, targetSplit, sourceParts, targetParts; + if(geometry instanceof OpenLayers.Geometry.LineString) { + targetParts = []; + sourceParts = [geometry]; + for(var i=0, len=this.components.length; i 1) { + sourceSplit = true; + } else { + sourceParts = []; + } + if(targetParts && targetParts.length > 1) { + targetSplit = true; + } else { + targetParts = []; + } + if(sourceSplit || targetSplit) { + if(mutual) { + results = [sourceParts, targetParts]; + } else { + results = targetParts; + } + } + return results; + }, + + CLASS_NAME: "OpenLayers.Geometry.MultiLineString" +}); +/* ====================================================================== + OpenLayers/Format.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/BaseTypes/Class.js + * @requires OpenLayers/Util.js + */ + +/** + * Class: OpenLayers.Format + * Base class for format reading/writing a variety of formats. Subclasses + * of OpenLayers.Format are expected to have read and write methods. + */ +OpenLayers.Format = OpenLayers.Class({ + + /** + * Property: options + * {Object} A reference to options passed to the constructor. + */ + options: null, + + /** + * APIProperty: externalProjection + * {} When passed a externalProjection and + * internalProjection, the format will reproject the geometries it + * reads or writes. The externalProjection is the projection used by + * the content which is passed into read or which comes out of write. + * In order to reproject, a projection transformation function for the + * specified projections must be available. This support may be + * provided via proj4js or via a custom transformation function. See + * {} for more information on + * custom transformations. + */ + externalProjection: null, + + /** + * APIProperty: internalProjection + * {} When passed a externalProjection and + * internalProjection, the format will reproject the geometries it + * reads or writes. The internalProjection is the projection used by + * the geometries which are returned by read or which are passed into + * write. In order to reproject, a projection transformation function + * for the specified projections must be available. This support may be + * provided via proj4js or via a custom transformation function. See + * {} for more information on + * custom transformations. + */ + internalProjection: null, + + /** + * APIProperty: data + * {Object} When is true, this is the parsed string sent to + * . + */ + data: null, + + /** + * APIProperty: keepData + * {Object} Maintain a reference () to the most recently read data. + * Default is false. + */ + keepData: false, + + /** + * Constructor: OpenLayers.Format + * Instances of this class are not useful. See one of the subclasses. + * + * Parameters: + * options - {Object} An optional object with properties to set on the + * format + * + * Valid options: + * keepData - {Boolean} If true, upon , the data property will be + * set to the parsed object (e.g. the json or xml object). + * + * Returns: + * An instance of OpenLayers.Format + */ + initialize: function(options) { + OpenLayers.Util.extend(this, options); + this.options = options; + }, + + /** + * APIMethod: destroy + * Clean up. + */ + destroy: function() { + }, + + /** + * Method: read + * Read data from a string, and return an object whose type depends on the + * subclass. + * + * Parameters: + * data - {string} Data to read/parse. + * + * Returns: + * Depends on the subclass + */ + read: function(data) { + throw new Error('Read not implemented.'); + }, + + /** + * Method: write + * Accept an object, and return a string. + * + * Parameters: + * object - {Object} Object to be serialized + * + * Returns: + * {String} A string representation of the object. + */ + write: function(object) { + throw new Error('Write not implemented.'); + }, + + CLASS_NAME: "OpenLayers.Format" +}); +/* ====================================================================== + OpenLayers/Format/XML.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Format.js + */ + +/** + * Class: OpenLayers.Format.XML + * Read and write XML. For cross-browser XML generation, use methods on an + * instance of the XML format class instead of on document. + * The DOM creation and traversing methods exposed here all mimic the + * W3C XML DOM methods. Create a new parser with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Format.XML = OpenLayers.Class(OpenLayers.Format, { + + /** + * Property: namespaces + * {Object} Mapping of namespace aliases to namespace URIs. Properties + * of this object should not be set individually. Read-only. All + * XML subclasses should have their own namespaces object. Use + * to add or set a namespace alias after construction. + */ + namespaces: null, + + /** + * Property: namespaceAlias + * {Object} Mapping of namespace URI to namespace alias. This object + * is read-only. Use to add or set a namespace alias. + */ + namespaceAlias: null, + + /** + * Property: defaultPrefix + * {String} The default namespace alias for creating element nodes. + */ + defaultPrefix: null, + + /** + * Property: readers + * Contains public functions, grouped by namespace prefix, that will + * be applied when a namespaced node is found matching the function + * name. The function will be applied in the scope of this parser + * with two arguments: the node being read and a context object passed + * from the parent. + */ + readers: {}, + + /** + * Property: writers + * As a compliment to the property, this structure contains public + * writing functions grouped by namespace alias and named like the + * node names they produce. + */ + writers: {}, + + /** + * Property: xmldom + * {XMLDom} If this browser uses ActiveX, this will be set to a XMLDOM + * object. It is not intended to be a browser sniffing property. + * Instead, the xmldom property is used instead of document + * where namespaced node creation methods are not supported. In all + * other browsers, this remains null. + */ + xmldom: null, + + /** + * Constructor: OpenLayers.Format.XML + * Construct an XML parser. The parser is used to read and write XML. + * Reading XML from a string returns a DOM element. Writing XML from + * a DOM element returns a string. + * + * Parameters: + * options - {Object} Optional object whose properties will be set on + * the object. + */ + initialize: function(options) { + if(window.ActiveXObject) { + this.xmldom = new ActiveXObject("Microsoft.XMLDOM"); + } + OpenLayers.Format.prototype.initialize.apply(this, [options]); + // clone the namespace object and set all namespace aliases + this.namespaces = OpenLayers.Util.extend({}, this.namespaces); + this.namespaceAlias = {}; + for(var alias in this.namespaces) { + this.namespaceAlias[this.namespaces[alias]] = alias; + } + }, + + /** + * APIMethod: destroy + * Clean up. + */ + destroy: function() { + this.xmldom = null; + OpenLayers.Format.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: setNamespace + * Set a namespace alias and URI for the format. + * + * Parameters: + * alias - {String} The namespace alias (prefix). + * uri - {String} The namespace URI. + */ + setNamespace: function(alias, uri) { + this.namespaces[alias] = uri; + this.namespaceAlias[uri] = alias; + }, + + /** + * APIMethod: read + * Deserialize a XML string and return a DOM node. + * + * Parameters: + * text - {String} A XML string + + * Returns: + * {DOMElement} A DOM node + */ + read: function(text) { + var index = text.indexOf('<'); + if(index > 0) { + text = text.substring(index); + } + var node = OpenLayers.Util.Try( + OpenLayers.Function.bind(( + function() { + var xmldom; + /** + * Since we want to be able to call this method on the prototype + * itself, this.xmldom may not exist even if in IE. + */ + if(window.ActiveXObject && !this.xmldom) { + xmldom = new ActiveXObject("Microsoft.XMLDOM"); + } else { + xmldom = this.xmldom; + + } + xmldom.loadXML(text); + return xmldom; + } + ), this), + function() { + return new DOMParser().parseFromString(text, 'text/xml'); + }, + function() { + var req = new XMLHttpRequest(); + req.open("GET", "data:" + "text/xml" + + ";charset=utf-8," + encodeURIComponent(text), false); + if(req.overrideMimeType) { + req.overrideMimeType("text/xml"); + } + req.send(null); + return req.responseXML; + } + ); + + if(this.keepData) { + this.data = node; + } + + return node; + }, + + /** + * APIMethod: write + * Serialize a DOM node into a XML string. + * + * Parameters: + * node - {DOMElement} A DOM node. + * + * Returns: + * {String} The XML string representation of the input node. + */ + write: function(node) { + var data; + if(this.xmldom) { + data = node.xml; + } else { + var serializer = new XMLSerializer(); + if (node.nodeType == 1) { + // Add nodes to a document before serializing. Everything else + // is serialized as is. This may need more work. See #1218 . + var doc = document.implementation.createDocument("", "", null); + if (doc.importNode) { + node = doc.importNode(node, true); + } + doc.appendChild(node); + data = serializer.serializeToString(doc); + } else { + data = serializer.serializeToString(node); + } + } + return data; + }, + + /** + * APIMethod: createElementNS + * Create a new element with namespace. This node can be appended to + * another node with the standard node.appendChild method. For + * cross-browser support, this method must be used instead of + * document.createElementNS. + * + * Parameters: + * uri - {String} Namespace URI for the element. + * name - {String} The qualified name of the element (prefix:localname). + * + * Returns: + * {Element} A DOM element with namespace. + */ + createElementNS: function(uri, name) { + var element; + if(this.xmldom) { + if(typeof uri == "string") { + element = this.xmldom.createNode(1, name, uri); + } else { + element = this.xmldom.createNode(1, name, ""); + } + } else { + element = document.createElementNS(uri, name); + } + return element; + }, + + /** + * APIMethod: createDocumentFragment + * Create a document fragment node that can be appended to another node + * created by createElementNS. This will call + * document.createDocumentFragment outside of IE. In IE, the ActiveX + * object's createDocumentFragment method is used. + * + * Returns: + * {Element} A document fragment. + */ + createDocumentFragment: function() { + var element; + if (this.xmldom) { + element = this.xmldom.createDocumentFragment(); + } else { + element = document.createDocumentFragment(); + } + return element; + }, + + /** + * APIMethod: createTextNode + * Create a text node. This node can be appended to another node with + * the standard node.appendChild method. For cross-browser support, + * this method must be used instead of document.createTextNode. + * + * Parameters: + * text - {String} The text of the node. + * + * Returns: + * {DOMElement} A DOM text node. + */ + createTextNode: function(text) { + var node; + if (typeof text !== "string") { + text = String(text); + } + if(this.xmldom) { + node = this.xmldom.createTextNode(text); + } else { + node = document.createTextNode(text); + } + return node; + }, + + /** + * APIMethod: getElementsByTagNameNS + * Get a list of elements on a node given the namespace URI and local name. + * To return all nodes in a given namespace, use '*' for the name + * argument. To return all nodes of a given (local) name, regardless + * of namespace, use '*' for the uri argument. + * + * Parameters: + * node - {Element} Node on which to search for other nodes. + * uri - {String} Namespace URI. + * name - {String} Local name of the tag (without the prefix). + * + * Returns: + * {NodeList} A node list or array of elements. + */ + getElementsByTagNameNS: function(node, uri, name) { + var elements = []; + if(node.getElementsByTagNameNS) { + elements = node.getElementsByTagNameNS(uri, name); + } else { + // brute force method + var allNodes = node.getElementsByTagName("*"); + var potentialNode, fullName; + for(var i=0, len=allNodes.length; i method. + * value - {String} Optional text to be appended as a text node. + * + * Returns: + * {Element} An element node. + */ + createElementNSPlus: function(name, options) { + options = options || {}; + // order of prefix preference + // 1. in the uri option + // 2. in the prefix option + // 3. in the qualified name + // 4. from the defaultPrefix + var uri = options.uri || this.namespaces[options.prefix]; + if(!uri) { + var loc = name.indexOf(":"); + uri = this.namespaces[name.substring(0, loc)]; + } + if(!uri) { + uri = this.namespaces[this.defaultPrefix]; + } + var node = this.createElementNS(uri, name); + if(options.attributes) { + this.setAttributes(node, options.attributes); + } + var value = options.value; + if(value != null) { + node.appendChild(this.createTextNode(value)); + } + return node; + }, + + /** + * Method: setAttributes + * Set multiple attributes given key value pairs from an object. + * + * Parameters: + * node - {Element} An element node. + * obj - {Object || Array} An object whose properties represent attribute + * names and values represent attribute values. If an attribute name + * is a qualified name ("prefix:local"), the prefix will be looked up + * in the parsers {namespaces} object. If the prefix is found, + * setAttributeNS will be used instead of setAttribute. + */ + setAttributes: function(node, obj) { + var value, uri; + for(var name in obj) { + if(obj[name] != null && obj[name].toString) { + value = obj[name].toString(); + // check for qualified attribute name ("prefix:local") + uri = this.namespaces[name.substring(0, name.indexOf(":"))] || null; + this.setAttributeNS(node, uri, name, value); + } + } + }, + + /** + * Method: readNode + * Shorthand for applying one of the named readers given the node + * namespace and local name. Readers take two args (node, obj) and + * generally extend or modify the second. + * + * Parameters: + * node - {DOMElement} The node to be read (required). + * obj - {Object} The object to be modified (optional). + * + * Returns: + * {Object} The input object, modified (or a new one if none was provided). + */ + readNode: function(node, obj) { + if(!obj) { + obj = {}; + } + var group = this.readers[node.namespaceURI ? this.namespaceAlias[node.namespaceURI]: this.defaultPrefix]; + if(group) { + var local = node.localName || node.nodeName.split(":").pop(); + var reader = group[local] || group["*"]; + if(reader) { + reader.apply(this, [node, obj]); + } + } + return obj; + }, + + /** + * Method: readChildNodes + * Shorthand for applying the named readers to all children of a node. + * For each child of type 1 (element), is called. + * + * Parameters: + * node - {DOMElement} The node to be read (required). + * obj - {Object} The object to be modified (optional). + * + * Returns: + * {Object} The input object, modified. + */ + readChildNodes: function(node, obj) { + if(!obj) { + obj = {}; + } + var children = node.childNodes; + var child; + for(var i=0, len=children.length; i group. If a local name is used (e.g. "Name") then + * the namespace of the parent is assumed. If a local name is used + * and no parent is supplied, then the default namespace is assumed. + * obj - {Object} Structure containing data for the writer. + * parent - {DOMElement} Result will be appended to this node. If no parent + * is supplied, the node will not be appended to anything. + * + * Returns: + * {DOMElement} The child node. + */ + writeNode: function(name, obj, parent) { + var prefix, local; + var split = name.indexOf(":"); + if(split > 0) { + prefix = name.substring(0, split); + local = name.substring(split + 1); + } else { + if(parent) { + prefix = this.namespaceAlias[parent.namespaceURI]; + } else { + prefix = this.defaultPrefix; + } + local = name; + } + var child = this.writers[prefix][local].apply(this, [obj]); + if(parent) { + parent.appendChild(child); + } + return child; + }, + + /** + * APIMethod: getChildEl + * Get the first child element. Optionally only return the first child + * if it matches the given name and namespace URI. + * + * Parameters: + * node - {DOMElement} The parent node. + * name - {String} Optional node name (local) to search for. + * uri - {String} Optional namespace URI to search for. + * + * Returns: + * {DOMElement} The first child. Returns null if no element is found, if + * something significant besides an element is found, or if the element + * found does not match the optional name and uri. + */ + getChildEl: function(node, name, uri) { + return node && this.getThisOrNextEl(node.firstChild, name, uri); + }, + + /** + * APIMethod: getNextEl + * Get the next sibling element. Optionally get the first sibling only + * if it matches the given local name and namespace URI. + * + * Parameters: + * node - {DOMElement} The node. + * name - {String} Optional local name of the sibling to search for. + * uri - {String} Optional namespace URI of the sibling to search for. + * + * Returns: + * {DOMElement} The next sibling element. Returns null if no element is + * found, something significant besides an element is found, or the + * found element does not match the optional name and uri. + */ + getNextEl: function(node, name, uri) { + return node && this.getThisOrNextEl(node.nextSibling, name, uri); + }, + + /** + * Method: getThisOrNextEl + * Return this node or the next element node. Optionally get the first + * sibling with the given local name or namespace URI. + * + * Parameters: + * node - {DOMElement} The node. + * name - {String} Optional local name of the sibling to search for. + * uri - {String} Optional namespace URI of the sibling to search for. + * + * Returns: + * {DOMElement} The next sibling element. Returns null if no element is + * found, something significant besides an element is found, or the + * found element does not match the query. + */ + getThisOrNextEl: function(node, name, uri) { + outer: for(var sibling=node; sibling; sibling=sibling.nextSibling) { + switch(sibling.nodeType) { + case 1: // Element + if((!name || name === (sibling.localName || sibling.nodeName.split(":").pop())) && + (!uri || uri === sibling.namespaceURI)) { + // matches + break outer; + } + sibling = null; + break outer; + case 3: // Text + if(/^\s*$/.test(sibling.nodeValue)) { + break; + } + case 4: // CDATA + case 6: // ENTITY_NODE + case 12: // NOTATION_NODE + case 10: // DOCUMENT_TYPE_NODE + case 11: // DOCUMENT_FRAGMENT_NODE + sibling = null; + break outer; + } // ignore comments and processing instructions + } + return sibling || null; + }, + + /** + * APIMethod: lookupNamespaceURI + * Takes a prefix and returns the namespace URI associated with it on the given + * node if found (and null if not). Supplying null for the prefix will + * return the default namespace. + * + * For browsers that support it, this calls the native lookupNamesapceURI + * function. In other browsers, this is an implementation of + * http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespaceURI. + * + * For browsers that don't support the attribute.ownerElement property, this + * method cannot be called on attribute nodes. + * + * Parameters: + * node - {DOMElement} The node from which to start looking. + * prefix - {String} The prefix to lookup or null to lookup the default namespace. + * + * Returns: + * {String} The namespace URI for the given prefix. Returns null if the prefix + * cannot be found or the node is the wrong type. + */ + lookupNamespaceURI: function(node, prefix) { + var uri = null; + if(node) { + if(node.lookupNamespaceURI) { + uri = node.lookupNamespaceURI(prefix); + } else { + outer: switch(node.nodeType) { + case 1: // ELEMENT_NODE + if(node.namespaceURI !== null && node.prefix === prefix) { + uri = node.namespaceURI; + break outer; + } + var len = node.attributes.length; + if(len) { + var attr; + for(var i=0; i on the instance. On other browsers, this will + * either return an existing or create a new shared document (see + * ). + * + * Returns: + * {XMLDocument} + */ + getXMLDoc: function() { + if (!OpenLayers.Format.XML.document && !this.xmldom) { + if (document.implementation && document.implementation.createDocument) { + OpenLayers.Format.XML.document = + document.implementation.createDocument("", "", null); + } else if (!this.xmldom && window.ActiveXObject) { + this.xmldom = new ActiveXObject("Microsoft.XMLDOM"); + } + } + return OpenLayers.Format.XML.document || this.xmldom; + }, + + CLASS_NAME: "OpenLayers.Format.XML" + +}); + +OpenLayers.Format.XML.CONTENT_TYPE = {EMPTY: 0, SIMPLE: 1, COMPLEX: 2, MIXED: 3}; + +/** + * APIFunction: OpenLayers.Format.XML.lookupNamespaceURI + * Takes a prefix and returns the namespace URI associated with it on the given + * node if found (and null if not). Supplying null for the prefix will + * return the default namespace. + * + * For browsers that support it, this calls the native lookupNamesapceURI + * function. In other browsers, this is an implementation of + * http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespaceURI. + * + * For browsers that don't support the attribute.ownerElement property, this + * method cannot be called on attribute nodes. + * + * Parameters: + * node - {DOMElement} The node from which to start looking. + * prefix - {String} The prefix to lookup or null to lookup the default namespace. + * + * Returns: + * {String} The namespace URI for the given prefix. Returns null if the prefix + * cannot be found or the node is the wrong type. + */ +OpenLayers.Format.XML.lookupNamespaceURI = OpenLayers.Function.bind( + OpenLayers.Format.XML.prototype.lookupNamespaceURI, + OpenLayers.Format.XML.prototype +); + +/** + * Property: OpenLayers.Format.XML.document + * {XMLDocument} XML document to reuse for creating non-HTML compliant nodes, + * like document.createCDATASection. + */ +OpenLayers.Format.XML.document = null; +/* ====================================================================== + OpenLayers/Format/OGCExceptionReport.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Format/XML.js + */ + +/** + * Class: OpenLayers.Format.OGCExceptionReport + * Class to read exception reports for various OGC services and versions. + * + * Inherits from: + * - + */ +OpenLayers.Format.OGCExceptionReport = OpenLayers.Class(OpenLayers.Format.XML, { + + /** + * Property: namespaces + * {Object} Mapping of namespace aliases to namespace URIs. + */ + namespaces: { + ogc: "http://www.opengis.net/ogc" + }, + + /** + * Property: regExes + * Compiled regular expressions for manipulating strings. + */ + regExes: { + trimSpace: (/^\s*|\s*$/g), + removeSpace: (/\s*/g), + splitSpace: (/\s+/), + trimComma: (/\s*,\s*/g) + }, + + /** + * Property: defaultPrefix + */ + defaultPrefix: "ogc", + + /** + * Constructor: OpenLayers.Format.OGCExceptionReport + * Create a new parser for OGC exception reports. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + + /** + * APIMethod: read + * Read OGC exception report data from a string, and return an object with + * information about the exceptions. + * + * Parameters: + * data - {String} or {DOMElement} data to read/parse. + * + * Returns: + * {Object} Information about the exceptions that occurred. + */ + read: function(data) { + var result; + if(typeof data == "string") { + data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); + } + var root = data.documentElement; + var exceptionInfo = {exceptionReport: null}; + if (root) { + this.readChildNodes(data, exceptionInfo); + if (exceptionInfo.exceptionReport === null) { + // fall-back to OWSCommon since this is a common output format for exceptions + // we cannot easily use the ows readers directly since they differ for 1.0 and 1.1 + exceptionInfo = new OpenLayers.Format.OWSCommon().read(data); + } + } + return exceptionInfo; + }, + + /** + * Property: readers + * Contains public functions, grouped by namespace prefix, that will + * be applied when a namespaced node is found matching the function + * name. The function will be applied in the scope of this parser + * with two arguments: the node being read and a context object passed + * from the parent. + */ + readers: { + "ogc": { + "ServiceExceptionReport": function(node, obj) { + obj.exceptionReport = {exceptions: []}; + this.readChildNodes(node, obj.exceptionReport); + }, + "ServiceException": function(node, exceptionReport) { + var exception = { + code: node.getAttribute("code"), + locator: node.getAttribute("locator"), + text: this.getChildValue(node) + }; + exceptionReport.exceptions.push(exception); + } + } + }, + + CLASS_NAME: "OpenLayers.Format.OGCExceptionReport" + +}); +/* ====================================================================== + OpenLayers/Format/XML/VersionedOGC.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Format/XML.js + * @requires OpenLayers/Format/OGCExceptionReport.js + */ + +/** + * Class: OpenLayers.Format.XML.VersionedOGC + * Base class for versioned formats, i.e. a format which supports multiple + * versions. + * + * To enable checking if parsing succeeded, you will need to define a property + * called errorProperty on the parser you want to check. The parser will then + * check the returned object to see if that property is present. If it is, it + * assumes the parsing was successful. If it is not present (or is null), it will + * pass the document through an OGCExceptionReport parser. + * + * If errorProperty is undefined for the parser, this error checking mechanism + * will be disabled. + * + * + * + * Inherits from: + * - + */ +OpenLayers.Format.XML.VersionedOGC = OpenLayers.Class(OpenLayers.Format.XML, { + + /** + * APIProperty: defaultVersion + * {String} Version number to assume if none found. + */ + defaultVersion: null, + + /** + * APIProperty: version + * {String} Specify a version string if one is known. + */ + version: null, + + /** + * APIProperty: profile + * {String} If provided, use a custom profile. + */ + profile: null, + + /** + * APIProperty: allowFallback + * {Boolean} If a profiled parser cannot be found for the returned version, + * use a non-profiled parser as the fallback. Application code using this + * should take into account that the return object structure might be + * missing the specifics of the profile. Defaults to false. + */ + allowFallback: false, + + /** + * Property: name + * {String} The name of this parser, this is the part of the CLASS_NAME + * except for "OpenLayers.Format." + */ + name: null, + + /** + * APIProperty: stringifyOutput + * {Boolean} If true, write will return a string otherwise a DOMElement. + * Default is false. + */ + stringifyOutput: false, + + /** + * Property: parser + * {Object} Instance of the versioned parser. Cached for multiple read and + * write calls of the same version. + */ + parser: null, + + /** + * Constructor: OpenLayers.Format.XML.VersionedOGC. + * Constructor. + * + * Parameters: + * options - {Object} Optional object whose properties will be set on + * the object. + */ + initialize: function(options) { + OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); + var className = this.CLASS_NAME; + this.name = className.substring(className.lastIndexOf(".")+1); + }, + + /** + * Method: getVersion + * Returns the version to use. Subclasses can override this function + * if a different version detection is needed. + * + * Parameters: + * root - {DOMElement} + * options - {Object} Optional configuration object. + * + * Returns: + * {String} The version to use. + */ + getVersion: function(root, options) { + var version; + // read + if (root) { + version = this.version; + if(!version) { + version = root.getAttribute("version"); + if(!version) { + version = this.defaultVersion; + } + } + } else { // write + version = (options && options.version) || + this.version || this.defaultVersion; + } + return version; + }, + + /** + * Method: getParser + * Get an instance of the cached parser if available, otherwise create one. + * + * Parameters: + * version - {String} + * + * Returns: + * {} + */ + getParser: function(version) { + version = version || this.defaultVersion; + var profile = this.profile ? "_" + this.profile : ""; + if(!this.parser || this.parser.VERSION != version) { + var format = OpenLayers.Format[this.name][ + "v" + version.replace(/\./g, "_") + profile + ]; + if(!format) { + if (profile !== "" && this.allowFallback) { + // fallback to the non-profiled version of the parser + profile = ""; + format = OpenLayers.Format[this.name][ + "v" + version.replace(/\./g, "_") + ]; + } + if (!format) { + throw "Can't find a " + this.name + " parser for version " + + version + profile; + } + } + this.parser = new format(this.options); + } + return this.parser; + }, + + /** + * APIMethod: write + * Write a document. + * + * Parameters: + * obj - {Object} An object representing the document. + * options - {Object} Optional configuration object. + * + * Returns: + * {String} The document as a string + */ + write: function(obj, options) { + var version = this.getVersion(null, options); + this.parser = this.getParser(version); + var root = this.parser.write(obj, options); + if (this.stringifyOutput === false) { + return root; + } else { + return OpenLayers.Format.XML.prototype.write.apply(this, [root]); + } + }, + + /** + * APIMethod: read + * Read a doc and return an object representing the document. + * + * Parameters: + * data - {String | DOMElement} Data to read. + * options - {Object} Options for the reader. + * + * Returns: + * {Object} An object representing the document. + */ + read: function(data, options) { + if(typeof data == "string") { + data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); + } + var root = data.documentElement; + var version = this.getVersion(root); + this.parser = this.getParser(version); // Select the parser + var obj = this.parser.read(data, options); // Parse the data + + var errorProperty = this.parser.errorProperty || null; + if (errorProperty !== null && obj[errorProperty] === undefined) { + // an error must have happened, so parse it and report back + var format = new OpenLayers.Format.OGCExceptionReport(); + obj.error = format.read(data); + } + obj.version = version; + return obj; + }, + + CLASS_NAME: "OpenLayers.Format.XML.VersionedOGC" +}); +/* ====================================================================== + OpenLayers/Feature.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/BaseTypes/Class.js + * @requires OpenLayers/Util.js + */ + +/** + * Class: OpenLayers.Feature + * Features are combinations of geography and attributes. The OpenLayers.Feature + * class specifically combines a marker and a lonlat. + */ +OpenLayers.Feature = OpenLayers.Class({ + + /** + * Property: layer + * {} + */ + layer: null, + + /** + * Property: id + * {String} + */ + id: null, + + /** + * Property: lonlat + * {} + */ + lonlat: null, + + /** + * Property: data + * {Object} + */ + data: null, + + /** + * Property: marker + * {} + */ + marker: null, + + /** + * APIProperty: popupClass + * {} The class which will be used to instantiate + * a new Popup. Default is . + */ + popupClass: null, + + /** + * Property: popup + * {} + */ + popup: null, + + /** + * Constructor: OpenLayers.Feature + * Constructor for features. + * + * Parameters: + * layer - {} + * lonlat - {} + * data - {Object} + * + * Returns: + * {} + */ + initialize: function(layer, lonlat, data) { + this.layer = layer; + this.lonlat = lonlat; + this.data = (data != null) ? data : {}; + this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_"); + }, + + /** + * Method: destroy + * nullify references to prevent circular references and memory leaks + */ + destroy: function() { + + //remove the popup from the map + if ((this.layer != null) && (this.layer.map != null)) { + if (this.popup != null) { + this.layer.map.removePopup(this.popup); + } + } + // remove the marker from the layer + if (this.layer != null && this.marker != null) { + this.layer.removeMarker(this.marker); + } + + this.layer = null; + this.id = null; + this.lonlat = null; + this.data = null; + if (this.marker != null) { + this.destroyMarker(this.marker); + this.marker = null; + } + if (this.popup != null) { + this.destroyPopup(this.popup); + this.popup = null; + } + }, + + /** + * Method: onScreen + * + * Returns: + * {Boolean} Whether or not the feature is currently visible on screen + * (based on its 'lonlat' property) + */ + onScreen:function() { + + var onScreen = false; + if ((this.layer != null) && (this.layer.map != null)) { + var screenBounds = this.layer.map.getExtent(); + onScreen = screenBounds.containsLonLat(this.lonlat); + } + return onScreen; + }, + + + /** + * Method: createMarker + * Based on the data associated with the Feature, create and return a marker object. + * + * Returns: + * {} A Marker Object created from the 'lonlat' and 'icon' properties + * set in this.data. If no 'lonlat' is set, returns null. If no + * 'icon' is set, OpenLayers.Marker() will load the default image. + * + * Note - this.marker is set to return value + * + */ + createMarker: function() { + + if (this.lonlat != null) { + this.marker = new OpenLayers.Marker(this.lonlat, this.data.icon); + } + return this.marker; + }, + + /** + * Method: destroyMarker + * Destroys marker. + * If user overrides the createMarker() function, s/he should be able + * to also specify an alternative function for destroying it + */ + destroyMarker: function() { + this.marker.destroy(); + }, + + /** + * Method: createPopup + * Creates a popup object created from the 'lonlat', 'popupSize', + * and 'popupContentHTML' properties set in this.data. It uses + * this.marker.icon as default anchor. + * + * If no 'lonlat' is set, returns null. + * If no this.marker has been created, no anchor is sent. + * + * Note - the returned popup object is 'owned' by the feature, so you + * cannot use the popup's destroy method to discard the popup. + * Instead, you must use the feature's destroyPopup + * + * Note - this.popup is set to return value + * + * Parameters: + * closeBox - {Boolean} create popup with closebox or not + * + * Returns: + * {} Returns the created popup, which is also set + * as 'popup' property of this feature. Will be of whatever type + * specified by this feature's 'popupClass' property, but must be + * of type . + * + */ + createPopup: function(closeBox) { + + if (this.lonlat != null) { + if (!this.popup) { + var anchor = (this.marker) ? this.marker.icon : null; + var popupClass = this.popupClass ? + this.popupClass : OpenLayers.Popup.Anchored; + this.popup = new popupClass(this.id + "_popup", + this.lonlat, + this.data.popupSize, + this.data.popupContentHTML, + anchor, + closeBox); + } + if (this.data.overflow != null) { + this.popup.contentDiv.style.overflow = this.data.overflow; + } + + this.popup.feature = this; + } + return this.popup; + }, + + + /** + * Method: destroyPopup + * Destroys the popup created via createPopup. + * + * As with the marker, if user overrides the createPopup() function, s/he + * should also be able to override the destruction + */ + destroyPopup: function() { + if (this.popup) { + this.popup.feature = null; + this.popup.destroy(); + this.popup = null; + } + }, + + CLASS_NAME: "OpenLayers.Feature" +}); +/* ====================================================================== + OpenLayers/Feature/Vector.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +// TRASH THIS +OpenLayers.State = { + /** states */ + UNKNOWN: 'Unknown', + INSERT: 'Insert', + UPDATE: 'Update', + DELETE: 'Delete' +}; + +/** + * @requires OpenLayers/Feature.js + * @requires OpenLayers/Util.js + */ + +/** + * Class: OpenLayers.Feature.Vector + * Vector features use the OpenLayers.Geometry classes as geometry description. + * They have an 'attributes' property, which is the data object, and a 'style' + * property, the default values of which are defined in the + * objects. + * + * Inherits from: + * - + */ +OpenLayers.Feature.Vector = OpenLayers.Class(OpenLayers.Feature, { + + /** + * Property: fid + * {String} + */ + fid: null, + + /** + * APIProperty: geometry + * {} + */ + geometry: null, + + /** + * APIProperty: attributes + * {Object} This object holds arbitrary, serializable properties that + * describe the feature. + */ + attributes: null, + + /** + * Property: bounds + * {} The box bounding that feature's geometry, that + * property can be set by an object when + * deserializing the feature, so in most cases it represents an + * information set by the server. + */ + bounds: null, + + /** + * Property: state + * {String} + */ + state: null, + + /** + * APIProperty: style + * {Object} + */ + style: null, + + /** + * APIProperty: url + * {String} If this property is set it will be taken into account by + * {} when upadting or deleting the feature. + */ + url: null, + + /** + * Property: renderIntent + * {String} rendering intent currently being used + */ + renderIntent: "default", + + /** + * APIProperty: modified + * {Object} An object with the originals of the geometry and attributes of + * the feature, if they were changed. Currently this property is only read + * by , and written by + * , which sets the geometry property. + * Applications can set the originals of modified attributes in the + * attributes property. Note that applications have to check if this + * object and the attributes property is already created before using it. + * After a change made with ModifyFeature, this object could look like + * + * (code) + * { + * geometry: >Object + * } + * (end) + * + * When an application has made changes to feature attributes, it could + * have set the attributes to something like this: + * + * (code) + * { + * attributes: { + * myAttribute: "original" + * } + * } + * (end) + * + * Note that only checks for truthy values in + * *modified.geometry* and the attribute names in *modified.attributes*, + * but it is recommended to set the original values (and not just true) as + * attribute value, so applications could use this information to undo + * changes. + */ + modified: null, + + /** + * Constructor: OpenLayers.Feature.Vector + * Create a vector feature. + * + * Parameters: + * geometry - {} The geometry that this feature + * represents. + * attributes - {Object} An optional object that will be mapped to the + * property. + * style - {Object} An optional style object. + */ + initialize: function(geometry, attributes, style) { + OpenLayers.Feature.prototype.initialize.apply(this, + [null, null, attributes]); + this.lonlat = null; + this.geometry = geometry ? geometry : null; + this.state = null; + this.attributes = {}; + if (attributes) { + this.attributes = OpenLayers.Util.extend(this.attributes, + attributes); + } + this.style = style ? style : null; + }, + + /** + * Method: destroy + * nullify references to prevent circular references and memory leaks + */ + destroy: function() { + if (this.layer) { + this.layer.removeFeatures(this); + this.layer = null; + } + + this.geometry = null; + this.modified = null; + OpenLayers.Feature.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: clone + * Create a clone of this vector feature. Does not set any non-standard + * properties. + * + * Returns: + * {} An exact clone of this vector feature. + */ + clone: function () { + return new OpenLayers.Feature.Vector( + this.geometry ? this.geometry.clone() : null, + this.attributes, + this.style); + }, + + /** + * Method: onScreen + * Determine whether the feature is within the map viewport. This method + * tests for an intersection between the geometry and the viewport + * bounds. If a more effecient but less precise geometry bounds + * intersection is desired, call the method with the boundsOnly + * parameter true. + * + * Parameters: + * boundsOnly - {Boolean} Only test whether a feature's bounds intersects + * the viewport bounds. Default is false. If false, the feature's + * geometry must intersect the viewport for onScreen to return true. + * + * Returns: + * {Boolean} The feature is currently visible on screen (optionally + * based on its bounds if boundsOnly is true). + */ + onScreen:function(boundsOnly) { + var onScreen = false; + if(this.layer && this.layer.map) { + var screenBounds = this.layer.map.getExtent(); + if(boundsOnly) { + var featureBounds = this.geometry.getBounds(); + onScreen = screenBounds.intersectsBounds(featureBounds); + } else { + var screenPoly = screenBounds.toGeometry(); + onScreen = screenPoly.intersects(this.geometry); + } + } + return onScreen; + }, + + /** + * Method: getVisibility + * Determine whether the feature is displayed or not. It may not displayed + * because: + * - its style display property is set to 'none', + * - it doesn't belong to any layer, + * - the styleMap creates a symbolizer with display property set to 'none' + * for it, + * - the layer which it belongs to is not visible. + * + * Returns: + * {Boolean} The feature is currently displayed. + */ + getVisibility: function() { + return !(this.style && this.style.display == 'none' || + !this.layer || + this.layer && this.layer.styleMap && + this.layer.styleMap.createSymbolizer(this, this.renderIntent).display == 'none' || + this.layer && !this.layer.getVisibility()); + }, + + /** + * Method: createMarker + * HACK - we need to decide if all vector features should be able to + * create markers + * + * Returns: + * {} For now just returns null + */ + createMarker: function() { + return null; + }, + + /** + * Method: destroyMarker + * HACK - we need to decide if all vector features should be able to + * delete markers + * + * If user overrides the createMarker() function, s/he should be able + * to also specify an alternative function for destroying it + */ + destroyMarker: function() { + // pass + }, + + /** + * Method: createPopup + * HACK - we need to decide if all vector features should be able to + * create popups + * + * Returns: + * {} For now just returns null + */ + createPopup: function() { + return null; + }, + + /** + * Method: atPoint + * Determins whether the feature intersects with the specified location. + * + * Parameters: + * lonlat - {|Object} OpenLayers.LonLat or an + * object with a 'lon' and 'lat' properties. + * toleranceLon - {float} Optional tolerance in Geometric Coords + * toleranceLat - {float} Optional tolerance in Geographic Coords + * + * Returns: + * {Boolean} Whether or not the feature is at the specified location + */ + atPoint: function(lonlat, toleranceLon, toleranceLat) { + var atPoint = false; + if(this.geometry) { + atPoint = this.geometry.atPoint(lonlat, toleranceLon, + toleranceLat); + } + return atPoint; + }, + + /** + * Method: destroyPopup + * HACK - we need to decide if all vector features should be able to + * delete popups + */ + destroyPopup: function() { + // pass + }, + + /** + * Method: move + * Moves the feature and redraws it at its new location + * + * Parameters: + * location - { or } the + * location to which to move the feature. + */ + move: function(location) { + + if(!this.layer || !this.geometry.move){ + //do nothing if no layer or immoveable geometry + return undefined; + } + + var pixel; + if (location.CLASS_NAME == "OpenLayers.LonLat") { + pixel = this.layer.getViewPortPxFromLonLat(location); + } else { + pixel = location; + } + + var lastPixel = this.layer.getViewPortPxFromLonLat(this.geometry.getBounds().getCenterLonLat()); + var res = this.layer.map.getResolution(); + this.geometry.move(res * (pixel.x - lastPixel.x), + res * (lastPixel.y - pixel.y)); + this.layer.drawFeature(this); + return lastPixel; + }, + + /** + * Method: toState + * Sets the new state + * + * Parameters: + * state - {String} + */ + toState: function(state) { + if (state == OpenLayers.State.UPDATE) { + switch (this.state) { + case OpenLayers.State.UNKNOWN: + case OpenLayers.State.DELETE: + this.state = state; + break; + case OpenLayers.State.UPDATE: + case OpenLayers.State.INSERT: + break; + } + } else if (state == OpenLayers.State.INSERT) { + switch (this.state) { + case OpenLayers.State.UNKNOWN: + break; + default: + this.state = state; + break; + } + } else if (state == OpenLayers.State.DELETE) { + switch (this.state) { + case OpenLayers.State.INSERT: + // the feature should be destroyed + break; + case OpenLayers.State.DELETE: + break; + case OpenLayers.State.UNKNOWN: + case OpenLayers.State.UPDATE: + this.state = state; + break; + } + } else if (state == OpenLayers.State.UNKNOWN) { + this.state = state; + } + }, + + CLASS_NAME: "OpenLayers.Feature.Vector" +}); + + +/** + * Constant: OpenLayers.Feature.Vector.style + * OpenLayers features can have a number of style attributes. The 'default' + * style will typically be used if no other style is specified. These + * styles correspond for the most part, to the styling properties defined + * by the SVG standard. + * Information on fill properties: http://www.w3.org/TR/SVG/painting.html#FillProperties + * Information on stroke properties: http://www.w3.org/TR/SVG/painting.html#StrokeProperties + * + * Symbolizer properties: + * fill - {Boolean} Set to false if no fill is desired. + * fillColor - {String} Hex fill color. Default is "#ee9900". + * fillOpacity - {Number} Fill opacity (0-1). Default is 0.4 + * stroke - {Boolean} Set to false if no stroke is desired. + * strokeColor - {String} Hex stroke color. Default is "#ee9900". + * strokeOpacity - {Number} Stroke opacity (0-1). Default is 1. + * strokeWidth - {Number} Pixel stroke width. Default is 1. + * strokeLinecap - {String} Stroke cap type. Default is "round". [butt | round | square] + * strokeDashstyle - {String} Stroke dash style. Default is "solid". [dot | dash | dashdot | longdash | longdashdot | solid] + * graphic - {Boolean} Set to false if no graphic is desired. + * pointRadius - {Number} Pixel point radius. Default is 6. + * pointerEvents - {String} Default is "visiblePainted". + * cursor - {String} Default is "". + * externalGraphic - {String} Url to an external graphic that will be used for rendering points. + * graphicWidth - {Number} Pixel width for sizing an external graphic. + * graphicHeight - {Number} Pixel height for sizing an external graphic. + * graphicOpacity - {Number} Opacity (0-1) for an external graphic. + * graphicXOffset - {Number} Pixel offset along the positive x axis for displacing an external graphic. + * graphicYOffset - {Number} Pixel offset along the positive y axis for displacing an external graphic. + * rotation - {Number} For point symbolizers, this is the rotation of a graphic in the clockwise direction about its center point (or any point off center as specified by graphicXOffset and graphicYOffset). + * graphicZIndex - {Number} The integer z-index value to use in rendering. + * graphicName - {String} Named graphic to use when rendering points. Supported values include "circle" (default), + * "square", "star", "x", "cross", "triangle". + * graphicTitle - {String} Tooltip when hovering over a feature. *deprecated*, use title instead + * title - {String} Tooltip when hovering over a feature. Not supported by the canvas renderer. + * backgroundGraphic - {String} Url to a graphic to be used as the background under an externalGraphic. + * backgroundGraphicZIndex - {Number} The integer z-index value to use in rendering the background graphic. + * backgroundXOffset - {Number} The x offset (in pixels) for the background graphic. + * backgroundYOffset - {Number} The y offset (in pixels) for the background graphic. + * backgroundHeight - {Number} The height of the background graphic. If not provided, the graphicHeight will be used. + * backgroundWidth - {Number} The width of the background width. If not provided, the graphicWidth will be used. + * label - {String} The text for an optional label. For browsers that use the canvas renderer, this requires either + * fillText or mozDrawText to be available. + * labelAlign - {String} Label alignment. This specifies the insertion point relative to the text. It is a string + * composed of two characters. The first character is for the horizontal alignment, the second for the vertical + * alignment. Valid values for horizontal alignment: "l"=left, "c"=center, "r"=right. Valid values for vertical + * alignment: "t"=top, "m"=middle, "b"=bottom. Example values: "lt", "cm", "rb". Default is "cm". + * labelXOffset - {Number} Pixel offset along the positive x axis for displacing the label. Not supported by the canvas renderer. + * labelYOffset - {Number} Pixel offset along the positive y axis for displacing the label. Not supported by the canvas renderer. + * labelSelect - {Boolean} If set to true, labels will be selectable using SelectFeature or similar controls. + * Default is false. + * labelOutlineColor - {String} The color of the label outline. Default is 'white'. Only supported by the canvas & SVG renderers. + * labelOutlineWidth - {Number} The width of the label outline. Default is 3, set to 0 or null to disable. Only supported by the SVG renderers. + * labelOutlineOpacity - {Number} The opacity (0-1) of the label outline. Default is fontOpacity. Only supported by the canvas & SVG renderers. + * fontColor - {String} The font color for the label, to be provided like CSS. + * fontOpacity - {Number} Opacity (0-1) for the label + * fontFamily - {String} The font family for the label, to be provided like in CSS. + * fontSize - {String} The font size for the label, to be provided like in CSS. + * fontStyle - {String} The font style for the label, to be provided like in CSS. + * fontWeight - {String} The font weight for the label, to be provided like in CSS. + * display - {String} Symbolizers will have no effect if display is set to "none". All other values have no effect. + */ +OpenLayers.Feature.Vector.style = { + 'default': { + fillColor: "#ee9900", + fillOpacity: 0.4, + hoverFillColor: "white", + hoverFillOpacity: 0.8, + strokeColor: "#ee9900", + strokeOpacity: 1, + strokeWidth: 1, + strokeLinecap: "round", + strokeDashstyle: "solid", + hoverStrokeColor: "red", + hoverStrokeOpacity: 1, + hoverStrokeWidth: 0.2, + pointRadius: 6, + hoverPointRadius: 1, + hoverPointUnit: "%", + pointerEvents: "visiblePainted", + cursor: "inherit", + fontColor: "#000000", + labelAlign: "cm", + labelOutlineColor: "white", + labelOutlineWidth: 3 + }, + 'select': { + fillColor: "blue", + fillOpacity: 0.4, + hoverFillColor: "white", + hoverFillOpacity: 0.8, + strokeColor: "blue", + strokeOpacity: 1, + strokeWidth: 2, + strokeLinecap: "round", + strokeDashstyle: "solid", + hoverStrokeColor: "red", + hoverStrokeOpacity: 1, + hoverStrokeWidth: 0.2, + pointRadius: 6, + hoverPointRadius: 1, + hoverPointUnit: "%", + pointerEvents: "visiblePainted", + cursor: "pointer", + fontColor: "#000000", + labelAlign: "cm", + labelOutlineColor: "white", + labelOutlineWidth: 3 + + }, + 'temporary': { + fillColor: "#66cccc", + fillOpacity: 0.2, + hoverFillColor: "white", + hoverFillOpacity: 0.8, + strokeColor: "#66cccc", + strokeOpacity: 1, + strokeLinecap: "round", + strokeWidth: 2, + strokeDashstyle: "solid", + hoverStrokeColor: "red", + hoverStrokeOpacity: 1, + hoverStrokeWidth: 0.2, + pointRadius: 6, + hoverPointRadius: 1, + hoverPointUnit: "%", + pointerEvents: "visiblePainted", + cursor: "inherit", + fontColor: "#000000", + labelAlign: "cm", + labelOutlineColor: "white", + labelOutlineWidth: 3 + + }, + 'delete': { + display: "none" + } +}; +/* ====================================================================== + OpenLayers/Style.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/BaseTypes/Class.js + * @requires OpenLayers/Util.js + * @requires OpenLayers/Feature/Vector.js + */ + +/** + * Class: OpenLayers.Style + * This class represents a UserStyle obtained + * from a SLD, containing styling rules. + */ +OpenLayers.Style = OpenLayers.Class({ + + /** + * Property: id + * {String} A unique id for this session. + */ + id: null, + + /** + * APIProperty: name + * {String} + */ + name: null, + + /** + * Property: title + * {String} Title of this style (set if included in SLD) + */ + title: null, + + /** + * Property: description + * {String} Description of this style (set if abstract is included in SLD) + */ + description: null, + + /** + * APIProperty: layerName + * {} name of the layer that this style belongs to, usually + * according to the NamedLayer attribute of an SLD document. + */ + layerName: null, + + /** + * APIProperty: isDefault + * {Boolean} + */ + isDefault: false, + + /** + * Property: rules + * {Array()} + */ + rules: null, + + /** + * APIProperty: context + * {Object} An optional object with properties that symbolizers' property + * values should be evaluated against. If no context is specified, + * feature.attributes will be used + */ + context: null, + + /** + * Property: defaultStyle + * {Object} hash of style properties to use as default for merging + * rule-based style symbolizers onto. If no rules are defined, + * createSymbolizer will return this style. If is set to + * true, the defaultStyle will only be taken into account if there are + * rules defined. + */ + defaultStyle: null, + + /** + * Property: defaultsPerSymbolizer + * {Boolean} If set to true, the will extend the symbolizer + * of every rule. Properties of the will also be used to set + * missing symbolizer properties if the symbolizer has stroke, fill or + * graphic set to true. Default is false. + */ + defaultsPerSymbolizer: false, + + /** + * Property: propertyStyles + * {Hash of Boolean} cache of style properties that need to be parsed for + * propertyNames. Property names are keys, values won't be used. + */ + propertyStyles: null, + + + /** + * Constructor: OpenLayers.Style + * Creates a UserStyle. + * + * Parameters: + * style - {Object} Optional hash of style properties that will be + * used as default style for this style object. This style + * applies if no rules are specified. Symbolizers defined in + * rules will extend this default style. + * options - {Object} An optional object with properties to set on the + * style. + * + * Valid options: + * rules - {Array()} List of rules to be added to the + * style. + * + * Returns: + * {} + */ + initialize: function(style, options) { + + OpenLayers.Util.extend(this, options); + this.rules = []; + if(options && options.rules) { + this.addRules(options.rules); + } + + // use the default style from OpenLayers.Feature.Vector if no style + // was given in the constructor + this.setDefaultStyle(style || + OpenLayers.Feature.Vector.style["default"]); + + this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_"); + }, + + /** + * APIMethod: destroy + * nullify references to prevent circular references and memory leaks + */ + destroy: function() { + for (var i=0, len=this.rules.length; i} feature to evaluate rules for + * + * Returns: + * {Object} symbolizer hash + */ + createSymbolizer: function(feature) { + var style = this.defaultsPerSymbolizer ? {} : this.createLiterals( + OpenLayers.Util.extend({}, this.defaultStyle), feature); + + var rules = this.rules; + + var rule, context; + var elseRules = []; + var appliedRules = false; + for(var i=0, len=rules.length; i 0) { + appliedRules = true; + for(var i=0, len=elseRules.length; i 0 && appliedRules == false) { + style.display = "none"; + } + + if (style.label != null && typeof style.label !== "string") { + style.label = String(style.label); + } + + return style; + }, + + /** + * Method: applySymbolizer + * + * Parameters: + * rule - {} + * style - {Object} + * feature - {} + * + * Returns: + * {Object} A style with new symbolizer applied. + */ + applySymbolizer: function(rule, style, feature) { + var symbolizerPrefix = feature.geometry ? + this.getSymbolizerPrefix(feature.geometry) : + OpenLayers.Style.SYMBOLIZER_PREFIXES[0]; + + var symbolizer = rule.symbolizer[symbolizerPrefix] || rule.symbolizer; + + if(this.defaultsPerSymbolizer === true) { + var defaults = this.defaultStyle; + OpenLayers.Util.applyDefaults(symbolizer, { + pointRadius: defaults.pointRadius + }); + if(symbolizer.stroke === true || symbolizer.graphic === true) { + OpenLayers.Util.applyDefaults(symbolizer, { + strokeWidth: defaults.strokeWidth, + strokeColor: defaults.strokeColor, + strokeOpacity: defaults.strokeOpacity, + strokeDashstyle: defaults.strokeDashstyle, + strokeLinecap: defaults.strokeLinecap + }); + } + if(symbolizer.fill === true || symbolizer.graphic === true) { + OpenLayers.Util.applyDefaults(symbolizer, { + fillColor: defaults.fillColor, + fillOpacity: defaults.fillOpacity + }); + } + if(symbolizer.graphic === true) { + OpenLayers.Util.applyDefaults(symbolizer, { + pointRadius: this.defaultStyle.pointRadius, + externalGraphic: this.defaultStyle.externalGraphic, + graphicName: this.defaultStyle.graphicName, + graphicOpacity: this.defaultStyle.graphicOpacity, + graphicWidth: this.defaultStyle.graphicWidth, + graphicHeight: this.defaultStyle.graphicHeight, + graphicXOffset: this.defaultStyle.graphicXOffset, + graphicYOffset: this.defaultStyle.graphicYOffset + }); + } + } + + // merge the style with the current style + return this.createLiterals( + OpenLayers.Util.extend(style, symbolizer), feature); + }, + + /** + * Method: createLiterals + * creates literals for all style properties that have an entry in + * . + * + * Parameters: + * style - {Object} style to create literals for. Will be modified + * inline. + * feature - {Object} + * + * Returns: + * {Object} the modified style + */ + createLiterals: function(style, feature) { + var context = OpenLayers.Util.extend({}, feature.attributes || feature.data); + OpenLayers.Util.extend(context, this.context); + + for (var i in this.propertyStyles) { + style[i] = OpenLayers.Style.createLiteral(style[i], context, feature, i); + } + return style; + }, + + /** + * Method: findPropertyStyles + * Looks into all rules for this style and the defaultStyle to collect + * all the style hash property names containing ${...} strings that have + * to be replaced using the createLiteral method before returning them. + * + * Returns: + * {Object} hash of property names that need createLiteral parsing. The + * name of the property is the key, and the value is true; + */ + findPropertyStyles: function() { + var propertyStyles = {}; + + // check the default style + var style = this.defaultStyle; + this.addPropertyStyles(propertyStyles, style); + + // walk through all rules to check for properties in their symbolizer + var rules = this.rules; + var symbolizer, value; + for (var i=0, len=rules.length; i)} + */ + addRules: function(rules) { + Array.prototype.push.apply(this.rules, rules); + this.propertyStyles = this.findPropertyStyles(); + }, + + /** + * APIMethod: setDefaultStyle + * Sets the default style for this style object. + * + * Parameters: + * style - {Object} Hash of style properties + */ + setDefaultStyle: function(style) { + this.defaultStyle = style; + this.propertyStyles = this.findPropertyStyles(); + }, + + /** + * Method: getSymbolizerPrefix + * Returns the correct symbolizer prefix according to the + * geometry type of the passed geometry + * + * Parameters: + * geometry - {} + * + * Returns: + * {String} key of the according symbolizer + */ + getSymbolizerPrefix: function(geometry) { + var prefixes = OpenLayers.Style.SYMBOLIZER_PREFIXES; + for (var i=0, len=prefixes.length; i} Clone of this style. + */ + clone: function() { + var options = OpenLayers.Util.extend({}, this); + // clone rules + if(this.rules) { + options.rules = []; + for(var i=0, len=this.rules.length; i} optional feature to pass to + * for evaluating functions in the + * context. + * property - {String} optional, name of the property for which the literal is + * being created for evaluating functions in the context. + * + * Returns: + * {String} the parsed value. In the example of the value parameter above, the + * result would be "foo valueOfBar", assuming that the passed feature has an + * attribute named "bar" with the value "valueOfBar". + */ +OpenLayers.Style.createLiteral = function(value, context, feature, property) { + if (typeof value == "string" && value.indexOf("${") != -1) { + value = OpenLayers.String.format(value, context, [feature, property]); + value = (isNaN(value) || !value) ? value : parseFloat(value); + } + return value; +}; + +/** + * Constant: OpenLayers.Style.SYMBOLIZER_PREFIXES + * {Array} prefixes of the sld symbolizers. These are the + * same as the main geometry types + */ +OpenLayers.Style.SYMBOLIZER_PREFIXES = ['Point', 'Line', 'Polygon', 'Text', + 'Raster']; +/* ====================================================================== + OpenLayers/Filter.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/BaseTypes/Class.js + * @requires OpenLayers/Util.js + * @requires OpenLayers/Style.js + */ + +/** + * Class: OpenLayers.Filter + * This class represents an OGC Filter. + */ +OpenLayers.Filter = OpenLayers.Class({ + + /** + * Constructor: OpenLayers.Filter + * This class represents a generic filter. + * + * Parameters: + * options - {Object} Optional object whose properties will be set on the + * instance. + * + * Returns: + * {} + */ + initialize: function(options) { + OpenLayers.Util.extend(this, options); + }, + + /** + * APIMethod: destroy + * Remove reference to anything added. + */ + destroy: function() { + }, + + /** + * APIMethod: evaluate + * Evaluates this filter in a specific context. Instances or subclasses + * are supposed to override this method. + * + * Parameters: + * context - {Object} Context to use in evaluating the filter. If a vector + * feature is provided, the feature.attributes will be used as context. + * + * Returns: + * {Boolean} The filter applies. + */ + evaluate: function(context) { + return true; + }, + + /** + * APIMethod: clone + * Clones this filter. Should be implemented by subclasses. + * + * Returns: + * {} Clone of this filter. + */ + clone: function() { + return null; + }, + + /** + * APIMethod: toString + * + * Returns: + * {String} Include in your build to get a CQL + * representation of the filter returned. Otherwise "[Object object]" + * will be returned. + */ + toString: function() { + var string; + if (OpenLayers.Format && OpenLayers.Format.CQL) { + string = OpenLayers.Format.CQL.prototype.write(this); + } else { + string = Object.prototype.toString.call(this); + } + return string; + }, + + CLASS_NAME: "OpenLayers.Filter" +}); +/* ====================================================================== + OpenLayers/Filter/FeatureId.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Filter.js + */ + +/** + * Class: OpenLayers.Filter.FeatureId + * This class represents a ogc:FeatureId Filter, as being used for rule-based SLD + * styling + * + * Inherits from: + * - + */ +OpenLayers.Filter.FeatureId = OpenLayers.Class(OpenLayers.Filter, { + + /** + * APIProperty: fids + * {Array(String)} Feature Ids to evaluate this rule against. + * To be passed inside the params object. + */ + fids: null, + + /** + * Property: type + * {String} Type to identify this filter. + */ + type: "FID", + + /** + * Constructor: OpenLayers.Filter.FeatureId + * Creates an ogc:FeatureId rule. + * + * Parameters: + * options - {Object} An optional object with properties to set on the + * rule + * + * Returns: + * {} + */ + initialize: function(options) { + this.fids = []; + OpenLayers.Filter.prototype.initialize.apply(this, [options]); + }, + + /** + * APIMethod: evaluate + * evaluates this rule for a specific feature + * + * Parameters: + * feature - {} feature to apply the rule to. + * For vector features, the check is run against the fid, + * for plain features against the id. + * + * Returns: + * {Boolean} true if the rule applies, false if it does not + */ + evaluate: function(feature) { + for (var i=0, len=this.fids.length; i} Clone of this filter. + */ + clone: function() { + var filter = new OpenLayers.Filter.FeatureId(); + OpenLayers.Util.extend(filter, this); + filter.fids = this.fids.slice(); + return filter; + }, + + CLASS_NAME: "OpenLayers.Filter.FeatureId" +}); +/* ====================================================================== + OpenLayers/Filter/Logical.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Filter.js + */ + +/** + * Class: OpenLayers.Filter.Logical + * This class represents ogc:And, ogc:Or and ogc:Not rules. + * + * Inherits from: + * - + */ +OpenLayers.Filter.Logical = OpenLayers.Class(OpenLayers.Filter, { + + /** + * APIProperty: filters + * {Array()} Child filters for this filter. + */ + filters: null, + + /** + * APIProperty: type + * {String} type of logical operator. Available types are: + * - OpenLayers.Filter.Logical.AND = "&&"; + * - OpenLayers.Filter.Logical.OR = "||"; + * - OpenLayers.Filter.Logical.NOT = "!"; + */ + type: null, + + /** + * Constructor: OpenLayers.Filter.Logical + * Creates a logical filter (And, Or, Not). + * + * Parameters: + * options - {Object} An optional object with properties to set on the + * filter. + * + * Returns: + * {} + */ + initialize: function(options) { + this.filters = []; + OpenLayers.Filter.prototype.initialize.apply(this, [options]); + }, + + /** + * APIMethod: destroy + * Remove reference to child filters. + */ + destroy: function() { + this.filters = null; + OpenLayers.Filter.prototype.destroy.apply(this); + }, + + /** + * APIMethod: evaluate + * Evaluates this filter in a specific context. + * + * Parameters: + * context - {Object} Context to use in evaluating the filter. A vector + * feature may also be provided to evaluate feature attributes in + * comparison filters or geometries in spatial filters. + * + * Returns: + * {Boolean} The filter applies. + */ + evaluate: function(context) { + var i, len; + switch(this.type) { + case OpenLayers.Filter.Logical.AND: + for (i=0, len=this.filters.length; i} Clone of this filter. + */ + clone: function() { + var filters = []; + for(var i=0, len=this.filters.length; i + */ +OpenLayers.Filter.Comparison = OpenLayers.Class(OpenLayers.Filter, { + + /** + * APIProperty: type + * {String} type: type of the comparison. This is one of + * - OpenLayers.Filter.Comparison.EQUAL_TO = "=="; + * - OpenLayers.Filter.Comparison.NOT_EQUAL_TO = "!="; + * - OpenLayers.Filter.Comparison.LESS_THAN = "<"; + * - OpenLayers.Filter.Comparison.GREATER_THAN = ">"; + * - OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO = "<="; + * - OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO = ">="; + * - OpenLayers.Filter.Comparison.BETWEEN = ".."; + * - OpenLayers.Filter.Comparison.LIKE = "~"; + * - OpenLayers.Filter.Comparison.IS_NULL = "NULL"; + */ + type: null, + + /** + * APIProperty: property + * {String} + * name of the context property to compare + */ + property: null, + + /** + * APIProperty: value + * {Number} or {String} + * comparison value for binary comparisons. In the case of a String, this + * can be a combination of text and propertyNames in the form + * "literal ${propertyName}" + */ + value: null, + + /** + * Property: matchCase + * {Boolean} Force case sensitive searches for EQUAL_TO and NOT_EQUAL_TO + * comparisons. The Filter Encoding 1.1 specification added a matchCase + * attribute to ogc:PropertyIsEqualTo and ogc:PropertyIsNotEqualTo + * elements. This property will be serialized with those elements only + * if using the v1.1.0 filter format. However, when evaluating filters + * here, the matchCase property will always be respected (for EQUAL_TO + * and NOT_EQUAL_TO). Default is true. + */ + matchCase: true, + + /** + * APIProperty: lowerBoundary + * {Number} or {String} + * lower boundary for between comparisons. In the case of a String, this + * can be a combination of text and propertyNames in the form + * "literal ${propertyName}" + */ + lowerBoundary: null, + + /** + * APIProperty: upperBoundary + * {Number} or {String} + * upper boundary for between comparisons. In the case of a String, this + * can be a combination of text and propertyNames in the form + * "literal ${propertyName}" + */ + upperBoundary: null, + + /** + * Constructor: OpenLayers.Filter.Comparison + * Creates a comparison rule. + * + * Parameters: + * options - {Object} An optional object with properties to set on the + * rule + * + * Returns: + * {} + */ + initialize: function(options) { + OpenLayers.Filter.prototype.initialize.apply(this, [options]); + // since matchCase on PropertyIsLike is not schema compliant, we only + // want to use this if explicitly asked for + if (this.type === OpenLayers.Filter.Comparison.LIKE + && options.matchCase === undefined) { + this.matchCase = null; + } + }, + + /** + * APIMethod: evaluate + * Evaluates this filter in a specific context. + * + * Parameters: + * context - {Object} Context to use in evaluating the filter. If a vector + * feature is provided, the feature.attributes will be used as context. + * + * Returns: + * {Boolean} The filter applies. + */ + evaluate: function(context) { + if (context instanceof OpenLayers.Feature.Vector) { + context = context.attributes; + } + var result = false; + var got = context[this.property]; + var exp; + switch(this.type) { + case OpenLayers.Filter.Comparison.EQUAL_TO: + exp = this.value; + if(!this.matchCase && + typeof got == "string" && typeof exp == "string") { + result = (got.toUpperCase() == exp.toUpperCase()); + } else { + result = (got == exp); + } + break; + case OpenLayers.Filter.Comparison.NOT_EQUAL_TO: + exp = this.value; + if(!this.matchCase && + typeof got == "string" && typeof exp == "string") { + result = (got.toUpperCase() != exp.toUpperCase()); + } else { + result = (got != exp); + } + break; + case OpenLayers.Filter.Comparison.LESS_THAN: + result = got < this.value; + break; + case OpenLayers.Filter.Comparison.GREATER_THAN: + result = got > this.value; + break; + case OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO: + result = got <= this.value; + break; + case OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO: + result = got >= this.value; + break; + case OpenLayers.Filter.Comparison.BETWEEN: + result = (got >= this.lowerBoundary) && + (got <= this.upperBoundary); + break; + case OpenLayers.Filter.Comparison.LIKE: + var regexp = new RegExp(this.value, "gi"); + result = regexp.test(got); + break; + case OpenLayers.Filter.Comparison.IS_NULL: + result = (got === null); + break; + } + return result; + }, + + /** + * APIMethod: value2regex + * Converts the value of this rule into a regular expression string, + * according to the wildcard characters specified. This method has to + * be called after instantiation of this class, if the value is not a + * regular expression already. + * + * Parameters: + * wildCard - {Char} wildcard character in the above value, default + * is "*" + * singleChar - {Char} single-character wildcard in the above value + * default is "." + * escapeChar - {Char} escape character in the above value, default is + * "!" + * + * Returns: + * {String} regular expression string + */ + value2regex: function(wildCard, singleChar, escapeChar) { + if (wildCard == ".") { + throw new Error("'.' is an unsupported wildCard character for " + + "OpenLayers.Filter.Comparison"); + } + + + // set UMN MapServer defaults for unspecified parameters + wildCard = wildCard ? wildCard : "*"; + singleChar = singleChar ? singleChar : "."; + escapeChar = escapeChar ? escapeChar : "!"; + + this.value = this.value.replace( + new RegExp("\\"+escapeChar+"(.|$)", "g"), "\\$1"); + this.value = this.value.replace( + new RegExp("\\"+singleChar, "g"), "."); + this.value = this.value.replace( + new RegExp("\\"+wildCard, "g"), ".*"); + this.value = this.value.replace( + new RegExp("\\\\.\\*", "g"), "\\"+wildCard); + this.value = this.value.replace( + new RegExp("\\\\\\.", "g"), "\\"+singleChar); + + return this.value; + }, + + /** + * Method: regex2value + * Convert the value of this rule from a regular expression string into an + * ogc literal string using a wildCard of *, a singleChar of ., and an + * escape of !. Leaves the property unmodified. + * + * Returns: + * {String} A string value. + */ + regex2value: function() { + + var value = this.value; + + // replace ! with !! + value = value.replace(/!/g, "!!"); + + // replace \. with !. (watching out for \\.) + value = value.replace(/(\\)?\\\./g, function($0, $1) { + return $1 ? $0 : "!."; + }); + + // replace \* with #* (watching out for \\*) + value = value.replace(/(\\)?\\\*/g, function($0, $1) { + return $1 ? $0 : "!*"; + }); + + // replace \\ with \ + value = value.replace(/\\\\/g, "\\"); + + // convert .* to * (the sequence #.* is not allowed) + value = value.replace(/\.\*/g, "*"); + + return value; + }, + + /** + * APIMethod: clone + * Clones this filter. + * + * Returns: + * {} Clone of this filter. + */ + clone: function() { + return OpenLayers.Util.extend(new OpenLayers.Filter.Comparison(), this); + }, + + CLASS_NAME: "OpenLayers.Filter.Comparison" +}); + + +OpenLayers.Filter.Comparison.EQUAL_TO = "=="; +OpenLayers.Filter.Comparison.NOT_EQUAL_TO = "!="; +OpenLayers.Filter.Comparison.LESS_THAN = "<"; +OpenLayers.Filter.Comparison.GREATER_THAN = ">"; +OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO = "<="; +OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO = ">="; +OpenLayers.Filter.Comparison.BETWEEN = ".."; +OpenLayers.Filter.Comparison.LIKE = "~"; +OpenLayers.Filter.Comparison.IS_NULL = "NULL"; +/* ====================================================================== + OpenLayers/Format/Filter.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Format/XML/VersionedOGC.js + * @requires OpenLayers/Filter/FeatureId.js + * @requires OpenLayers/Filter/Logical.js + * @requires OpenLayers/Filter/Comparison.js + */ + +/** + * Class: OpenLayers.Format.Filter + * Read/Write ogc:Filter. Create a new instance with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Format.Filter = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, { + + /** + * APIProperty: defaultVersion + * {String} Version number to assume if none found. Default is "1.0.0". + */ + defaultVersion: "1.0.0", + + /** + * APIMethod: write + * Write an ogc:Filter given a filter object. + * + * Parameters: + * filter - {} An filter. + * options - {Object} Optional configuration object. + * + * Returns: + * {Elment} An ogc:Filter element node. + */ + + /** + * APIMethod: read + * Read and Filter doc and return an object representing the Filter. + * + * Parameters: + * data - {String | DOMElement} Data to read. + * + * Returns: + * {} A filter object. + */ + + CLASS_NAME: "OpenLayers.Format.Filter" +}); +/* ====================================================================== + OpenLayers/Format/WFST.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Format.js + */ + +/** + * Function: OpenLayers.Format.WFST + * Used to create a versioned WFS protocol. Default version is 1.0.0. + * + * Returns: + * {} A WFST format of the given version. + */ +OpenLayers.Format.WFST = function(options) { + options = OpenLayers.Util.applyDefaults( + options, OpenLayers.Format.WFST.DEFAULTS + ); + var cls = OpenLayers.Format.WFST["v"+options.version.replace(/\./g, "_")]; + if(!cls) { + throw "Unsupported WFST version: " + options.version; + } + return new cls(options); +}; + +/** + * Constant: OpenLayers.Format.WFST.DEFAULTS + * {Object} Default properties for the WFST format. + */ +OpenLayers.Format.WFST.DEFAULTS = { + "version": "1.0.0" +}; +/* ====================================================================== + OpenLayers/Filter/Spatial.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Filter.js + */ + +/** + * Class: OpenLayers.Filter.Spatial + * This class represents a spatial filter. + * Currently implemented: BBOX, DWithin and Intersects + * + * Inherits from: + * - + */ +OpenLayers.Filter.Spatial = OpenLayers.Class(OpenLayers.Filter, { + + /** + * APIProperty: type + * {String} Type of spatial filter. + * + * The type should be one of: + * - OpenLayers.Filter.Spatial.BBOX + * - OpenLayers.Filter.Spatial.INTERSECTS + * - OpenLayers.Filter.Spatial.DWITHIN + * - OpenLayers.Filter.Spatial.WITHIN + * - OpenLayers.Filter.Spatial.CONTAINS + */ + type: null, + + /** + * APIProperty: property + * {String} Name of the context property to compare. + */ + property: null, + + /** + * APIProperty: value + * { || } The bounds or geometry + * to be used by the filter. Use bounds for BBOX filters and geometry + * for INTERSECTS or DWITHIN filters. + */ + value: null, + + /** + * APIProperty: distance + * {Number} The distance to use in a DWithin spatial filter. + */ + distance: null, + + /** + * APIProperty: distanceUnits + * {String} The units to use for the distance, e.g. 'm'. + */ + distanceUnits: null, + + /** + * Constructor: OpenLayers.Filter.Spatial + * Creates a spatial filter. + * + * Parameters: + * options - {Object} An optional object with properties to set on the + * filter. + * + * Returns: + * {} + */ + + /** + * Method: evaluate + * Evaluates this filter for a specific feature. + * + * Parameters: + * feature - {} feature to apply the filter to. + * + * Returns: + * {Boolean} The feature meets filter criteria. + */ + evaluate: function(feature) { + var intersect = false; + switch(this.type) { + case OpenLayers.Filter.Spatial.BBOX: + case OpenLayers.Filter.Spatial.INTERSECTS: + if(feature.geometry) { + var geom = this.value; + if(this.value.CLASS_NAME == "OpenLayers.Bounds") { + geom = this.value.toGeometry(); + } + if(feature.geometry.intersects(geom)) { + intersect = true; + } + } + break; + default: + throw new Error('evaluate is not implemented for this filter type.'); + } + return intersect; + }, + + /** + * APIMethod: clone + * Clones this filter. + * + * Returns: + * {} Clone of this filter. + */ + clone: function() { + var options = OpenLayers.Util.applyDefaults({ + value: this.value && this.value.clone && this.value.clone() + }, this); + return new OpenLayers.Filter.Spatial(options); + }, + CLASS_NAME: "OpenLayers.Filter.Spatial" +}); + +OpenLayers.Filter.Spatial.BBOX = "BBOX"; +OpenLayers.Filter.Spatial.INTERSECTS = "INTERSECTS"; +OpenLayers.Filter.Spatial.DWITHIN = "DWITHIN"; +OpenLayers.Filter.Spatial.WITHIN = "WITHIN"; +OpenLayers.Filter.Spatial.CONTAINS = "CONTAINS"; +/* ====================================================================== + OpenLayers/Format/WFST/v1.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Format/XML.js + * @requires OpenLayers/Format/WFST.js + * @requires OpenLayers/Filter/Spatial.js + * @requires OpenLayers/Filter/FeatureId.js + */ + +/** + * Class: OpenLayers.Format.WFST.v1 + * Superclass for WFST parsers. + * + * Inherits from: + * - + */ +OpenLayers.Format.WFST.v1 = OpenLayers.Class(OpenLayers.Format.XML, { + + /** + * Property: namespaces + * {Object} Mapping of namespace aliases to namespace URIs. + */ + namespaces: { + xlink: "http://www.w3.org/1999/xlink", + xsi: "http://www.w3.org/2001/XMLSchema-instance", + wfs: "http://www.opengis.net/wfs", + gml: "http://www.opengis.net/gml", + ogc: "http://www.opengis.net/ogc", + ows: "http://www.opengis.net/ows" + }, + + /** + * Property: defaultPrefix + */ + defaultPrefix: "wfs", + + /** + * Property: version + * {String} WFS version number. + */ + version: null, + + /** + * Property: schemaLocation + * {String} Schema location for a particular minor version. + */ + schemaLocations: null, + + /** + * APIProperty: srsName + * {String} URI for spatial reference system. + */ + srsName: null, + + /** + * APIProperty: extractAttributes + * {Boolean} Extract attributes from GML. Default is true. + */ + extractAttributes: true, + + /** + * APIProperty: xy + * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x) + * Changing is not recommended, a new Format should be instantiated. + */ + xy: true, + + /** + * Property: stateName + * {Object} Maps feature states to node names. + */ + stateName: null, + + /** + * Constructor: OpenLayers.Format.WFST.v1 + * Instances of this class are not created directly. Use the + * or + * constructor instead. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + initialize: function(options) { + // set state name mapping + this.stateName = {}; + this.stateName[OpenLayers.State.INSERT] = "wfs:Insert"; + this.stateName[OpenLayers.State.UPDATE] = "wfs:Update"; + this.stateName[OpenLayers.State.DELETE] = "wfs:Delete"; + OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); + }, + + /** + * Method: getSrsName + */ + getSrsName: function(feature, options) { + var srsName = options && options.srsName; + if(!srsName) { + if(feature && feature.layer) { + srsName = feature.layer.projection.getCode(); + } else { + srsName = this.srsName; + } + } + return srsName; + }, + + /** + * APIMethod: read + * Parse the response from a transaction. Because WFS is split into + * Transaction requests (create, update, and delete) and GetFeature + * requests (read), this method handles parsing of both types of + * responses. + * + * Parameters: + * data - {String | Document} The WFST document to read + * options - {Object} Options for the reader + * + * Valid options properties: + * output - {String} either "features" or "object". The default is + * "features", which means that the method will return an array of + * features. If set to "object", an object with a "features" property + * and other properties read by the parser will be returned. + * + * Returns: + * {Array | Object} Output depending on the output option. + */ + read: function(data, options) { + options = options || {}; + OpenLayers.Util.applyDefaults(options, { + output: "features" + }); + + if(typeof data == "string") { + data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); + } + if(data && data.nodeType == 9) { + data = data.documentElement; + } + var obj = {}; + if(data) { + this.readNode(data, obj, true); + } + if(obj.features && options.output === "features") { + obj = obj.features; + } + return obj; + }, + + /** + * Property: readers + * Contains public functions, grouped by namespace prefix, that will + * be applied when a namespaced node is found matching the function + * name. The function will be applied in the scope of this parser + * with two arguments: the node being read and a context object passed + * from the parent. + */ + readers: { + "wfs": { + "FeatureCollection": function(node, obj) { + obj.features = []; + this.readChildNodes(node, obj); + } + } + }, + + /** + * Method: write + * Given an array of features, write a WFS transaction. This assumes + * the features have a state property that determines the operation + * type - insert, update, or delete. + * + * Parameters: + * features - {Array()} A list of features. See + * below for a more detailed description of the influence of the + * feature's *modified* property. + * options - {Object} + * + * feature.modified rules: + * If a feature has a modified property set, the following checks will be + * made before a feature's geometry or attribute is included in an Update + * transaction: + * - *modified* is not set at all: The geometry and all attributes will be + * included. + * - *modified.geometry* is set (null or a geometry): The geometry will be + * included. If *modified.attributes* is not set, all attributes will + * be included. + * - *modified.attributes* is set: Only the attributes set (i.e. to null or + * a value) in *modified.attributes* will be included. + * If *modified.geometry* is not set, the geometry will not be included. + * + * Valid options include: + * - *multi* {Boolean} If set to true, geometries will be casted to + * Multi geometries before writing. + * + * Returns: + * {String} A serialized WFS transaction. + */ + write: function(features, options) { + var node = this.writeNode("wfs:Transaction", { + features:features, + options: options + }); + var value = this.schemaLocationAttr(); + if(value) { + this.setAttributeNS( + node, this.namespaces["xsi"], "xsi:schemaLocation", value + ); + } + return OpenLayers.Format.XML.prototype.write.apply(this, [node]); + }, + + /** + * Property: writers + * As a compliment to the readers property, this structure contains public + * writing functions grouped by namespace alias and named like the + * node names they produce. + */ + writers: { + "wfs": { + "GetFeature": function(options) { + var node = this.createElementNSPlus("wfs:GetFeature", { + attributes: { + service: "WFS", + version: this.version, + handle: options && options.handle, + outputFormat: options && options.outputFormat, + maxFeatures: options && options.maxFeatures, + "xsi:schemaLocation": this.schemaLocationAttr(options) + } + }); + if (typeof this.featureType == "string") { + this.writeNode("Query", options, node); + } else { + for (var i=0,len = this.featureType.length; i} + */ + setFilterProperty: function(filter) { + if(filter.filters) { + for(var i=0, len=filter.filters.length; i + * - + */ +OpenLayers.Geometry.Polygon = OpenLayers.Class( + OpenLayers.Geometry.Collection, { + + /** + * Property: componentTypes + * {Array(String)} An array of class names representing the types of + * components that the collection can include. A null value means the + * component types are not restricted. + */ + componentTypes: ["OpenLayers.Geometry.LinearRing"], + + /** + * Constructor: OpenLayers.Geometry.Polygon + * Constructor for a Polygon geometry. + * The first ring (this.component[0])is the outer bounds of the polygon and + * all subsequent rings (this.component[1-n]) are internal holes. + * + * + * Parameters: + * components - {Array()} + */ + + /** + * APIMethod: getArea + * Calculated by subtracting the areas of the internal holes from the + * area of the outer hole. + * + * Returns: + * {float} The area of the geometry + */ + getArea: function() { + var area = 0.0; + if ( this.components && (this.components.length > 0)) { + area += Math.abs(this.components[0].getArea()); + for (var i=1, len=this.components.length; i} The spatial reference system + * for the geometry coordinates. If not provided, Geographic/WGS84 is + * assumed. + * + * Reference: + * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for + * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion + * Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409 + * + * Returns: + * {float} The approximate geodesic area of the polygon in square meters. + */ + getGeodesicArea: function(projection) { + var area = 0.0; + if(this.components && (this.components.length > 0)) { + area += Math.abs(this.components[0].getGeodesicArea(projection)); + for(var i=1, len=this.components.length; i} + * + * Returns: + * {Boolean | Number} The point is inside the polygon. Returns 1 if the + * point is on an edge. Returns boolean otherwise. + */ + containsPoint: function(point) { + var numRings = this.components.length; + var contained = false; + if(numRings > 0) { + // check exterior ring - 1 means on edge, boolean otherwise + contained = this.components[0].containsPoint(point); + if(contained !== 1) { + if(contained && numRings > 1) { + // check interior rings + var hole; + for(var i=1; i} Any type of geometry. + * + * Returns: + * {Boolean} The input geometry intersects this one. + */ + intersects: function(geometry) { + var intersect = false; + var i, len; + if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") { + intersect = this.containsPoint(geometry); + } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LineString" || + geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") { + // check if rings/linestrings intersect + for(i=0, len=this.components.length; i} The target geometry. + * options - {Object} Optional properties for configuring the distance + * calculation. + * + * Valid options: + * details - {Boolean} Return details from the distance calculation. + * Default is false. + * edge - {Boolean} Calculate the distance from this geometry to the + * nearest edge of the target geometry. Default is true. If true, + * calling distanceTo from a geometry that is wholly contained within + * the target will result in a non-zero distance. If false, whenever + * geometries intersect, calling distanceTo will return 0. If false, + * details cannot be returned. + * + * Returns: + * {Number | Object} The distance between this geometry and the target. + * If details is true, the return will be an object with distance, + * x0, y0, x1, and y1 properties. The x0 and y0 properties represent + * the coordinates of the closest point on this geometry. The x1 and y1 + * properties represent the coordinates of the closest point on the + * target geometry. + */ + distanceTo: function(geometry, options) { + var edge = !(options && options.edge === false); + var result; + // this is the case where we might not be looking for distance to edge + if(!edge && this.intersects(geometry)) { + result = 0; + } else { + result = OpenLayers.Geometry.Collection.prototype.distanceTo.apply( + this, [geometry, options] + ); + } + return result; + }, + + CLASS_NAME: "OpenLayers.Geometry.Polygon" +}); + +/** + * APIMethod: createRegularPolygon + * Create a regular polygon around a radius. Useful for creating circles + * and the like. + * + * Parameters: + * origin - {} center of polygon. + * radius - {Float} distance to vertex, in map units. + * sides - {Integer} Number of sides. 20 approximates a circle. + * rotation - {Float} original angle of rotation, in degrees. + */ +OpenLayers.Geometry.Polygon.createRegularPolygon = function(origin, radius, sides, rotation) { + var angle = Math.PI * ((1/sides) - (1/2)); + if(rotation) { + angle += (rotation / 180) * Math.PI; + } + var rotatedAngle, x, y; + var points = []; + for(var i=0; i + * components. Create a new instance with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Geometry.MultiPolygon = OpenLayers.Class( + OpenLayers.Geometry.Collection, { + + /** + * Property: componentTypes + * {Array(String)} An array of class names representing the types of + * components that the collection can include. A null value means the + * component types are not restricted. + */ + componentTypes: ["OpenLayers.Geometry.Polygon"], + + /** + * Constructor: OpenLayers.Geometry.MultiPolygon + * Create a new MultiPolygon geometry + * + * Parameters: + * components - {Array()} An array of polygons + * used to generate the MultiPolygon + * + */ + + CLASS_NAME: "OpenLayers.Geometry.MultiPolygon" +}); +/* ====================================================================== + OpenLayers/Format/GML.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Format/XML.js + * @requires OpenLayers/Feature/Vector.js + * @requires OpenLayers/Geometry/Point.js + * @requires OpenLayers/Geometry/MultiPoint.js + * @requires OpenLayers/Geometry/LineString.js + * @requires OpenLayers/Geometry/MultiLineString.js + * @requires OpenLayers/Geometry/Polygon.js + * @requires OpenLayers/Geometry/MultiPolygon.js + */ + +/** + * Class: OpenLayers.Format.GML + * Read/Write GML. Create a new instance with the + * constructor. Supports the GML simple features profile. + * + * Inherits from: + * - + */ +OpenLayers.Format.GML = OpenLayers.Class(OpenLayers.Format.XML, { + + /** + * APIProperty: featureNS + * {String} Namespace used for feature attributes. Default is + * "http://mapserver.gis.umn.edu/mapserver". + */ + featureNS: "http://mapserver.gis.umn.edu/mapserver", + + /** + * APIProperty: featurePrefix + * {String} Namespace alias (or prefix) for feature nodes. Default is + * "feature". + */ + featurePrefix: "feature", + + /** + * APIProperty: featureName + * {String} Element name for features. Default is "featureMember". + */ + featureName: "featureMember", + + /** + * APIProperty: layerName + * {String} Name of data layer. Default is "features". + */ + layerName: "features", + + /** + * APIProperty: geometryName + * {String} Name of geometry element. Defaults to "geometry". + */ + geometryName: "geometry", + + /** + * APIProperty: collectionName + * {String} Name of featureCollection element. + */ + collectionName: "FeatureCollection", + + /** + * APIProperty: gmlns + * {String} GML Namespace. + */ + gmlns: "http://www.opengis.net/gml", + + /** + * APIProperty: extractAttributes + * {Boolean} Extract attributes from GML. + */ + extractAttributes: true, + + /** + * APIProperty: xy + * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x) + * Changing is not recommended, a new Format should be instantiated. + */ + xy: true, + + /** + * Constructor: OpenLayers.Format.GML + * Create a new parser for GML. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + initialize: function(options) { + // compile regular expressions once instead of every time they are used + this.regExes = { + trimSpace: (/^\s*|\s*$/g), + removeSpace: (/\s*/g), + splitSpace: (/\s+/), + trimComma: (/\s*,\s*/g) + }; + OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); + }, + + /** + * APIMethod: read + * Read data from a string, and return a list of features. + * + * Parameters: + * data - {String} or {DOMElement} data to read/parse. + * + * Returns: + * {Array()} An array of features. + */ + read: function(data) { + if(typeof data == "string") { + data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); + } + var featureNodes = this.getElementsByTagNameNS(data.documentElement, + this.gmlns, + this.featureName); + var features = []; + for(var i=0; i 0) { + // only deal with first geometry of this type + parser = this.parseGeometry[type.toLowerCase()]; + if(parser) { + geometry = parser.apply(this, [nodeList[0]]); + if (this.internalProjection && this.externalProjection) { + geometry.transform(this.externalProjection, + this.internalProjection); + } + } else { + throw new TypeError("Unsupported geometry type: " + type); + } + // stop looking for different geometry types + break; + } + } + + var bounds; + var boxNodes = this.getElementsByTagNameNS(node, this.gmlns, "Box"); + for(i=0; i} A point geometry. + */ + point: function(node) { + /** + * Three coordinate variations to consider: + * 1) x y z + * 2) x, y, z + * 3) xy + */ + var nodeList, coordString; + var coords = []; + + // look for + var nodeList = this.getElementsByTagNameNS(node, this.gmlns, "pos"); + if(nodeList.length > 0) { + coordString = nodeList[0].firstChild.nodeValue; + coordString = coordString.replace(this.regExes.trimSpace, ""); + coords = coordString.split(this.regExes.splitSpace); + } + + // look for + if(coords.length == 0) { + nodeList = this.getElementsByTagNameNS(node, this.gmlns, + "coordinates"); + if(nodeList.length > 0) { + coordString = nodeList[0].firstChild.nodeValue; + coordString = coordString.replace(this.regExes.removeSpace, + ""); + coords = coordString.split(","); + } + } + + // look for + if(coords.length == 0) { + nodeList = this.getElementsByTagNameNS(node, this.gmlns, + "coord"); + if(nodeList.length > 0) { + var xList = this.getElementsByTagNameNS(nodeList[0], + this.gmlns, "X"); + var yList = this.getElementsByTagNameNS(nodeList[0], + this.gmlns, "Y"); + if(xList.length > 0 && yList.length > 0) { + coords = [xList[0].firstChild.nodeValue, + yList[0].firstChild.nodeValue]; + } + } + } + + // preserve third dimension + if(coords.length == 2) { + coords[2] = null; + } + + if (this.xy) { + return new OpenLayers.Geometry.Point(coords[0], coords[1], + coords[2]); + } + else{ + return new OpenLayers.Geometry.Point(coords[1], coords[0], + coords[2]); + } + }, + + /** + * Method: parseGeometry.multipoint + * Given a GML node representing a multipoint geometry, create an + * OpenLayers multipoint geometry. + * + * Parameters: + * node - {DOMElement} A GML node. + * + * Returns: + * {} A multipoint geometry. + */ + multipoint: function(node) { + var nodeList = this.getElementsByTagNameNS(node, this.gmlns, + "Point"); + var components = []; + if(nodeList.length > 0) { + var point; + for(var i=0; i} A linestring geometry. + */ + linestring: function(node, ring) { + /** + * Two coordinate variations to consider: + * 1) x0 y0 z0 x1 y1 z1 + * 2) x0, y0, z0 x1, y1, z1 + */ + var nodeList, coordString; + var coords = []; + var points = []; + + // look for + nodeList = this.getElementsByTagNameNS(node, this.gmlns, "posList"); + if(nodeList.length > 0) { + coordString = this.getChildValue(nodeList[0]); + coordString = coordString.replace(this.regExes.trimSpace, ""); + coords = coordString.split(this.regExes.splitSpace); + var dim = parseInt(nodeList[0].getAttribute("dimension")); + var j, x, y, z; + for(var i=0; i + if(coords.length == 0) { + nodeList = this.getElementsByTagNameNS(node, this.gmlns, + "coordinates"); + if(nodeList.length > 0) { + coordString = this.getChildValue(nodeList[0]); + coordString = coordString.replace(this.regExes.trimSpace, + ""); + coordString = coordString.replace(this.regExes.trimComma, + ","); + var pointList = coordString.split(this.regExes.splitSpace); + for(var i=0; i} A multilinestring geometry. + */ + multilinestring: function(node) { + var nodeList = this.getElementsByTagNameNS(node, this.gmlns, + "LineString"); + var components = []; + if(nodeList.length > 0) { + var line; + for(var i=0; i} A polygon geometry. + */ + polygon: function(node) { + var nodeList = this.getElementsByTagNameNS(node, this.gmlns, + "LinearRing"); + var components = []; + if(nodeList.length > 0) { + // this assumes exterior ring first, inner rings after + var ring; + for(var i=0; i} A multipolygon geometry. + */ + multipolygon: function(node) { + var nodeList = this.getElementsByTagNameNS(node, this.gmlns, + "Polygon"); + var components = []; + if(nodeList.length > 0) { + var polygon; + for(var i=0; i 0) { + var coords = []; + + if(lpoint.length > 0) { + coordString = lpoint[0].firstChild.nodeValue; + coordString = coordString.replace(this.regExes.trimSpace, ""); + coords = coordString.split(this.regExes.splitSpace); + } + + if(coords.length == 2) { + coords[2] = null; + } + if (this.xy) { + var lowerPoint = new OpenLayers.Geometry.Point(coords[0], coords[1],coords[2]); + } else { + var lowerPoint = new OpenLayers.Geometry.Point(coords[1], coords[0],coords[2]); + } + } + + var upoint = this.getElementsByTagNameNS(node, this.gmlns, "upperCorner"); + if (upoint.length > 0) { + var coords = []; + + if(upoint.length > 0) { + coordString = upoint[0].firstChild.nodeValue; + coordString = coordString.replace(this.regExes.trimSpace, ""); + coords = coordString.split(this.regExes.splitSpace); + } + + if(coords.length == 2) { + coords[2] = null; + } + if (this.xy) { + var upperPoint = new OpenLayers.Geometry.Point(coords[0], coords[1],coords[2]); + } else { + var upperPoint = new OpenLayers.Geometry.Point(coords[1], coords[0],coords[2]); + } + } + + if (lowerPoint && upperPoint) { + components.push(new OpenLayers.Geometry.Point(lowerPoint.x, lowerPoint.y)); + components.push(new OpenLayers.Geometry.Point(upperPoint.x, lowerPoint.y)); + components.push(new OpenLayers.Geometry.Point(upperPoint.x, upperPoint.y)); + components.push(new OpenLayers.Geometry.Point(lowerPoint.x, upperPoint.y)); + components.push(new OpenLayers.Geometry.Point(lowerPoint.x, lowerPoint.y)); + + var ring = new OpenLayers.Geometry.LinearRing(components); + envelope = new OpenLayers.Geometry.Polygon([ring]); + } + return envelope; + }, + + /** + * Method: parseGeometry.box + * Given a GML node representing a box geometry, create an + * OpenLayers.Bounds. + * + * Parameters: + * node - {DOMElement} A GML node. + * + * Returns: + * {} A bounds representing the box. + */ + box: function(node) { + var nodeList = this.getElementsByTagNameNS(node, this.gmlns, + "coordinates"); + var coordString; + var coords, beginPoint = null, endPoint = null; + if (nodeList.length > 0) { + coordString = nodeList[0].firstChild.nodeValue; + coords = coordString.split(" "); + if (coords.length == 2) { + beginPoint = coords[0].split(","); + endPoint = coords[1].split(","); + } + } + if (beginPoint !== null && endPoint !== null) { + return new OpenLayers.Bounds(parseFloat(beginPoint[0]), + parseFloat(beginPoint[1]), + parseFloat(endPoint[0]), + parseFloat(endPoint[1]) ); + } + } + + }, + + /** + * Method: parseAttributes + * + * Parameters: + * node - {DOMElement} + * + * Returns: + * {Object} An attributes object. + */ + parseAttributes: function(node) { + var attributes = {}; + // assume attributes are children of the first type 1 child + var childNode = node.firstChild; + var children, i, child, grandchildren, grandchild, name, value; + while(childNode) { + if(childNode.nodeType == 1) { + // attributes are type 1 children with one type 3 child + children = childNode.childNodes; + for(i=0; i becomes + // {fieldname: null} + attributes[child.nodeName.split(":").pop()] = null; + } + } + } + break; + } + childNode = childNode.nextSibling; + } + return attributes; + }, + + /** + * APIMethod: write + * Generate a GML document string given a list of features. + * + * Parameters: + * features - {Array()} List of features to + * serialize into a string. + * + * Returns: + * {String} A string representing the GML document. + */ + write: function(features) { + if(!(OpenLayers.Util.isArray(features))) { + features = [features]; + } + var gml = this.createElementNS("http://www.opengis.net/wfs", + "wfs:" + this.collectionName); + for(var i=0; i} The feature to be built as GML. + * + * Returns: + * {DOMElement} A node reprensting the feature in GML. + */ + createFeatureXML: function(feature) { + var geometry = feature.geometry; + var geometryNode = this.buildGeometryNode(geometry); + var geomContainer = this.createElementNS(this.featureNS, + this.featurePrefix + ":" + + this.geometryName); + geomContainer.appendChild(geometryNode); + var featureNode = this.createElementNS(this.gmlns, + "gml:" + this.featureName); + var featureContainer = this.createElementNS(this.featureNS, + this.featurePrefix + ":" + + this.layerName); + var fid = feature.fid || feature.id; + featureContainer.setAttribute("fid", fid); + featureContainer.appendChild(geomContainer); + for(var attr in feature.attributes) { + var attrText = this.createTextNode(feature.attributes[attr]); + var nodename = attr.substring(attr.lastIndexOf(":") + 1); + var attrContainer = this.createElementNS(this.featureNS, + this.featurePrefix + ":" + + nodename); + attrContainer.appendChild(attrText); + featureContainer.appendChild(attrContainer); + } + featureNode.appendChild(featureContainer); + return featureNode; + }, + + /** + * APIMethod: buildGeometryNode + */ + buildGeometryNode: function(geometry) { + if (this.externalProjection && this.internalProjection) { + geometry = geometry.clone(); + geometry.transform(this.internalProjection, + this.externalProjection); + } + var className = geometry.CLASS_NAME; + var type = className.substring(className.lastIndexOf(".") + 1); + var builder = this.buildGeometry[type.toLowerCase()]; + return builder.apply(this, [geometry]); + }, + + /** + * Property: buildGeometry + * Object containing methods to do the actual geometry node building + * based on geometry type. + */ + buildGeometry: { + // TBD retrieve the srs from layer + // srsName is non-standard, so not including it until it's right. + // gml.setAttribute("srsName", + // "http://www.opengis.net/gml/srs/epsg.xml#4326"); + + /** + * Method: buildGeometry.point + * Given an OpenLayers point geometry, create a GML point. + * + * Parameters: + * geometry - {} A point geometry. + * + * Returns: + * {DOMElement} A GML point node. + */ + point: function(geometry) { + var gml = this.createElementNS(this.gmlns, "gml:Point"); + gml.appendChild(this.buildCoordinatesNode(geometry)); + return gml; + }, + + /** + * Method: buildGeometry.multipoint + * Given an OpenLayers multipoint geometry, create a GML multipoint. + * + * Parameters: + * geometry - {} A multipoint geometry. + * + * Returns: + * {DOMElement} A GML multipoint node. + */ + multipoint: function(geometry) { + var gml = this.createElementNS(this.gmlns, "gml:MultiPoint"); + var points = geometry.components; + var pointMember, pointGeom; + for(var i=0; i} A linestring geometry. + * + * Returns: + * {DOMElement} A GML linestring node. + */ + linestring: function(geometry) { + var gml = this.createElementNS(this.gmlns, "gml:LineString"); + gml.appendChild(this.buildCoordinatesNode(geometry)); + return gml; + }, + + /** + * Method: buildGeometry.multilinestring + * Given an OpenLayers multilinestring geometry, create a GML + * multilinestring. + * + * Parameters: + * geometry - {} A multilinestring + * geometry. + * + * Returns: + * {DOMElement} A GML multilinestring node. + */ + multilinestring: function(geometry) { + var gml = this.createElementNS(this.gmlns, "gml:MultiLineString"); + var lines = geometry.components; + var lineMember, lineGeom; + for(var i=0; i} A linearring geometry. + * + * Returns: + * {DOMElement} A GML linearring node. + */ + linearring: function(geometry) { + var gml = this.createElementNS(this.gmlns, "gml:LinearRing"); + gml.appendChild(this.buildCoordinatesNode(geometry)); + return gml; + }, + + /** + * Method: buildGeometry.polygon + * Given an OpenLayers polygon geometry, create a GML polygon. + * + * Parameters: + * geometry - {} A polygon geometry. + * + * Returns: + * {DOMElement} A GML polygon node. + */ + polygon: function(geometry) { + var gml = this.createElementNS(this.gmlns, "gml:Polygon"); + var rings = geometry.components; + var ringMember, ringGeom, type; + for(var i=0; i} A multipolygon + * geometry. + * + * Returns: + * {DOMElement} A GML multipolygon node. + */ + multipolygon: function(geometry) { + var gml = this.createElementNS(this.gmlns, "gml:MultiPolygon"); + var polys = geometry.components; + var polyMember, polyGeom; + for(var i=0; i} A bounds object. + * + * Returns: + * {DOMElement} A GML box node. + */ + bounds: function(bounds) { + var gml = this.createElementNS(this.gmlns, "gml:Box"); + gml.appendChild(this.buildCoordinatesNode(bounds)); + return gml; + } + }, + + /** + * Method: buildCoordinates + * builds the coordinates XmlNode + * (code) + * ... + * (end) + * + * Parameters: + * geometry - {} + * + * Returns: + * {XmlNode} created xmlNode + */ + buildCoordinatesNode: function(geometry) { + var coordinatesNode = this.createElementNS(this.gmlns, + "gml:coordinates"); + coordinatesNode.setAttribute("decimal", "."); + coordinatesNode.setAttribute("cs", ","); + coordinatesNode.setAttribute("ts", " "); + + var parts = []; + + if(geometry instanceof OpenLayers.Bounds){ + parts.push(geometry.left + "," + geometry.bottom); + parts.push(geometry.right + "," + geometry.top); + } else { + var points = (geometry.components) ? geometry.components : [geometry]; + for(var i=0; i + */ +OpenLayers.Format.GML.Base = OpenLayers.Class(OpenLayers.Format.XML, { + + /** + * Property: namespaces + * {Object} Mapping of namespace aliases to namespace URIs. + */ + namespaces: { + gml: "http://www.opengis.net/gml", + xlink: "http://www.w3.org/1999/xlink", + xsi: "http://www.w3.org/2001/XMLSchema-instance", + wfs: "http://www.opengis.net/wfs" // this is a convenience for reading wfs:FeatureCollection + }, + + /** + * Property: defaultPrefix + */ + defaultPrefix: "gml", + + /** + * Property: schemaLocation + * {String} Schema location for a particular minor version. + */ + schemaLocation: null, + + /** + * APIProperty: featureType + * {Array(String) or String} The local (without prefix) feature typeName(s). + */ + featureType: null, + + /** + * APIProperty: featureNS + * {String} The feature namespace. Must be set in the options at + * construction. + */ + featureNS: null, + + /** + * APIProperty: geometry + * {String} Name of geometry element. Defaults to "geometry". If null, it + * will be set on when the first geometry is parsed. + */ + geometryName: "geometry", + + /** + * APIProperty: extractAttributes + * {Boolean} Extract attributes from GML. Default is true. + */ + extractAttributes: true, + + /** + * APIProperty: srsName + * {String} URI for spatial reference system. This is optional for + * single part geometries and mandatory for collections and multis. + * If set, the srsName attribute will be written for all geometries. + * Default is null. + */ + srsName: null, + + /** + * APIProperty: xy + * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x) + * Changing is not recommended, a new Format should be instantiated. + */ + xy: true, + + /** + * Property: geometryTypes + * {Object} Maps OpenLayers geometry class names to GML element names. + * Use before accessing this property. + */ + geometryTypes: null, + + /** + * Property: singleFeatureType + * {Boolean} True if there is only 1 featureType, and not an array + * of featuretypes. + */ + singleFeatureType: null, + + /** + * Property: autoConfig + * {Boolean} Indicates if the format was configured without a , + * but auto-configured and during read. + * Subclasses making use of auto-configuration should make + * the first call to the method (usually in the read method) + * with true as 3rd argument, so the auto-configured featureType can be + * reset and the format can be reused for subsequent reads with data from + * different featureTypes. Set to false after read if you want to keep the + * auto-configured values. + */ + + /** + * Property: regExes + * Compiled regular expressions for manipulating strings. + */ + regExes: { + trimSpace: (/^\s*|\s*$/g), + removeSpace: (/\s*/g), + splitSpace: (/\s+/), + trimComma: (/\s*,\s*/g), + featureMember: (/^(.*:)?featureMembers?$/) + }, + + /** + * Constructor: OpenLayers.Format.GML.Base + * Instances of this class are not created directly. Use the + * or constructor + * instead. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + * + * Valid options properties: + * featureType - {Array(String) or String} Local (without prefix) feature + * typeName(s) (required for write). + * featureNS - {String} Feature namespace (required for write). + * geometryName - {String} Geometry element name (required for write). + */ + initialize: function(options) { + OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); + this.setGeometryTypes(); + if(options && options.featureNS) { + this.setNamespace("feature", options.featureNS); + } + this.singleFeatureType = !options || (typeof options.featureType === "string"); + }, + + /** + * Method: read + * + * Parameters: + * data - {DOMElement} A gml:featureMember element, a gml:featureMembers + * element, or an element containing either of the above at any level. + * + * Returns: + * {Array()} An array of features. + */ + read: function(data) { + if(typeof data == "string") { + data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); + } + if(data && data.nodeType == 9) { + data = data.documentElement; + } + var features = []; + this.readNode(data, {features: features}, true); + if(features.length == 0) { + // look for gml:featureMember elements + var elements = this.getElementsByTagNameNS( + data, this.namespaces.gml, "featureMember" + ); + if(elements.length) { + for(var i=0, len=elements.length; i 0) { + obj.bounds = container.components[0]; + } + }, + "Point": function(node, container) { + var obj = {points: []}; + this.readChildNodes(node, obj); + if(!container.components) { + container.components = []; + } + container.components.push(obj.points[0]); + }, + "coordinates": function(node, obj) { + var str = this.getChildValue(node).replace( + this.regExes.trimSpace, "" + ); + str = str.replace(this.regExes.trimComma, ","); + var pointList = str.split(this.regExes.splitSpace); + var coords; + var numPoints = pointList.length; + var points = new Array(numPoints); + for(var i=0; i) | OpenLayers.Feature.Vector} + * An array of features or a single feature. + * + * Returns: + * {String} Given an array of features, a doc with a gml:featureMembers + * element will be returned. Given a single feature, a doc with a + * gml:featureMember element will be returned. + */ + write: function(features) { + var name; + if(OpenLayers.Util.isArray(features)) { + name = "featureMembers"; + } else { + name = "featureMember"; + } + var root = this.writeNode("gml:" + name, features); + this.setAttributeNS( + root, this.namespaces["xsi"], + "xsi:schemaLocation", this.schemaLocation + ); + + return OpenLayers.Format.XML.prototype.write.apply(this, [root]); + }, + + /** + * Property: writers + * As a compliment to the readers property, this structure contains public + * writing functions grouped by namespace alias and named like the + * node names they produce. + */ + writers: { + "gml": { + "featureMember": function(feature) { + var node = this.createElementNSPlus("gml:featureMember"); + this.writeNode("feature:_typeName", feature, node); + return node; + }, + "MultiPoint": function(geometry) { + var node = this.createElementNSPlus("gml:MultiPoint"); + var components = geometry.components || [geometry]; + for(var i=0, ii=components.length; i mapping. + */ + setGeometryTypes: function() { + this.geometryTypes = { + "OpenLayers.Geometry.Point": "Point", + "OpenLayers.Geometry.MultiPoint": "MultiPoint", + "OpenLayers.Geometry.LineString": "LineString", + "OpenLayers.Geometry.MultiLineString": "MultiLineString", + "OpenLayers.Geometry.Polygon": "Polygon", + "OpenLayers.Geometry.MultiPolygon": "MultiPolygon", + "OpenLayers.Geometry.Collection": "GeometryCollection" + }; + }, + + CLASS_NAME: "OpenLayers.Format.GML.Base" + +}); +/* ====================================================================== + OpenLayers/Format/GML/v2.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Format/GML/Base.js + */ + +/** + * Class: OpenLayers.Format.GML.v2 + * Parses GML version 2. + * + * Inherits from: + * - + */ +OpenLayers.Format.GML.v2 = OpenLayers.Class(OpenLayers.Format.GML.Base, { + + /** + * Property: schemaLocation + * {String} Schema location for a particular minor version. + */ + schemaLocation: "http://www.opengis.net/gml http://schemas.opengis.net/gml/2.1.2/feature.xsd", + + /** + * Constructor: OpenLayers.Format.GML.v2 + * Create a parser for GML v2. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + * + * Valid options properties: + * featureType - {String} Local (without prefix) feature typeName (required). + * featureNS - {String} Feature namespace (required). + * geometryName - {String} Geometry element name. + */ + initialize: function(options) { + OpenLayers.Format.GML.Base.prototype.initialize.apply(this, [options]); + }, + + /** + * Property: readers + * Contains public functions, grouped by namespace prefix, that will + * be applied when a namespaced node is found matching the function + * name. The function will be applied in the scope of this parser + * with two arguments: the node being read and a context object passed + * from the parent. + */ + readers: { + "gml": OpenLayers.Util.applyDefaults({ + "outerBoundaryIs": function(node, container) { + var obj = {}; + this.readChildNodes(node, obj); + container.outer = obj.components[0]; + }, + "innerBoundaryIs": function(node, container) { + var obj = {}; + this.readChildNodes(node, obj); + container.inner.push(obj.components[0]); + }, + "Box": function(node, container) { + var obj = {}; + this.readChildNodes(node, obj); + if(!container.components) { + container.components = []; + } + var min = obj.points[0]; + var max = obj.points[1]; + container.components.push( + new OpenLayers.Bounds(min.x, min.y, max.x, max.y) + ); + } + }, OpenLayers.Format.GML.Base.prototype.readers["gml"]), + "feature": OpenLayers.Format.GML.Base.prototype.readers["feature"], + "wfs": OpenLayers.Format.GML.Base.prototype.readers["wfs"] + }, + + /** + * Method: write + * + * Parameters: + * features - {Array() | OpenLayers.Feature.Vector} + * An array of features or a single feature. + * + * Returns: + * {String} Given an array of features, a doc with a gml:featureMembers + * element will be returned. Given a single feature, a doc with a + * gml:featureMember element will be returned. + */ + write: function(features) { + var name; + if(OpenLayers.Util.isArray(features)) { + // GML2 only has abstract feature collections + // wfs provides a feature collection from a well-known schema + name = "wfs:FeatureCollection"; + } else { + name = "gml:featureMember"; + } + var root = this.writeNode(name, features); + this.setAttributeNS( + root, this.namespaces["xsi"], + "xsi:schemaLocation", this.schemaLocation + ); + + return OpenLayers.Format.XML.prototype.write.apply(this, [root]); + }, + + /** + * Property: writers + * As a compliment to the readers property, this structure contains public + * writing functions grouped by namespace alias and named like the + * node names they produce. + */ + writers: { + "gml": OpenLayers.Util.applyDefaults({ + "Point": function(geometry) { + var node = this.createElementNSPlus("gml:Point"); + this.writeNode("coordinates", [geometry], node); + return node; + }, + "coordinates": function(points) { + var numPoints = points.length; + var parts = new Array(numPoints); + var point; + for(var i=0; i + */ +OpenLayers.Filter.Function = OpenLayers.Class(OpenLayers.Filter, { + + /** + * APIProperty: name + * {String} Name of the function. + */ + name: null, + + /** + * APIProperty: params + * {Array( || String || Number)} Function parameters + * For now support only other Functions, String or Number + */ + params: null, + + /** + * Constructor: OpenLayers.Filter.Function + * Creates a filter function. + * + * Parameters: + * options - {Object} An optional object with properties to set on the + * function. + * + * Returns: + * {} + */ + + CLASS_NAME: "OpenLayers.Filter.Function" +}); + +/* ====================================================================== + OpenLayers/BaseTypes/Date.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/SingleFile.js + */ + +/** + * Namespace: OpenLayers.Date + * Contains implementations of Date.parse and date.toISOString that match the + * ECMAScript 5 specification for parsing RFC 3339 dates. + * http://tools.ietf.org/html/rfc3339 + */ +OpenLayers.Date = { + + /** + * APIProperty: dateRegEx + * The regex to be used for validating dates. You can provide your own + * regex for instance for adding support for years before BC. Default + * value is: /^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:(?:T(\d{1,2}):(\d{2}):(\d{2}(?:\.\d+)?)(Z|(?:[+-]\d{1,2}(?::(\d{2}))?)))|Z)?$/ + */ + dateRegEx: /^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:(?:T(\d{1,2}):(\d{2}):(\d{2}(?:\.\d+)?)(Z|(?:[+-]\d{1,2}(?::(\d{2}))?)))|Z)?$/, + + /** + * APIMethod: toISOString + * Generates a string representing a date. The format of the string follows + * the profile of ISO 8601 for date and time on the Internet (see + * http://tools.ietf.org/html/rfc3339). If the toISOString method is + * available on the Date prototype, that is used. The toISOString + * method for Date instances is defined in ECMA-262. + * + * Parameters: + * date - {Date} A date object. + * + * Returns: + * {String} A string representing the date (e.g. + * "2010-08-07T16:58:23.123Z"). If the date does not have a valid time + * (i.e. isNaN(date.getTime())) this method returns the string "Invalid + * Date". The ECMA standard says the toISOString method should throw + * RangeError in this case, but Firefox returns a string instead. For + * best results, use isNaN(date.getTime()) to determine date validity + * before generating date strings. + */ + toISOString: (function() { + if ("toISOString" in Date.prototype) { + return function(date) { + return date.toISOString(); + }; + } else { + return function(date) { + var str; + if (isNaN(date.getTime())) { + // ECMA-262 says throw RangeError, Firefox returns + // "Invalid Date" + str = "Invalid Date"; + } else { + str = + date.getUTCFullYear() + "-" + + OpenLayers.Number.zeroPad(date.getUTCMonth() + 1, 2) + "-" + + OpenLayers.Number.zeroPad(date.getUTCDate(), 2) + "T" + + OpenLayers.Number.zeroPad(date.getUTCHours(), 2) + ":" + + OpenLayers.Number.zeroPad(date.getUTCMinutes(), 2) + ":" + + OpenLayers.Number.zeroPad(date.getUTCSeconds(), 2) + "." + + OpenLayers.Number.zeroPad(date.getUTCMilliseconds(), 3) + "Z"; + } + return str; + }; + } + + })(), + + /** + * APIMethod: parse + * Generate a date object from a string. The format for the string follows + * the profile of ISO 8601 for date and time on the Internet (see + * http://tools.ietf.org/html/rfc3339). We don't call the native + * Date.parse because of inconsistency between implmentations. In + * Chrome, calling Date.parse with a string that doesn't contain any + * indication of the timezone (e.g. "2011"), the date is interpreted + * in local time. On Firefox, the assumption is UTC. + * + * Parameters: + * str - {String} A string representing the date (e.g. + * "2010", "2010-08", "2010-08-07", "2010-08-07T16:58:23.123Z", + * "2010-08-07T11:58:23.123-06"). + * + * Returns: + * {Date} A date object. If the string could not be parsed, an invalid + * date is returned (i.e. isNaN(date.getTime())). + */ + parse: function(str) { + var date; + var match = str.match(this.dateRegEx); + if (match && (match[1] || match[7])) { // must have at least year or time + var year = parseInt(match[1], 10) || 0; + var month = (parseInt(match[2], 10) - 1) || 0; + var day = parseInt(match[3], 10) || 1; + date = new Date(Date.UTC(year, month, day)); + // optional time + var type = match[7]; + if (type) { + var hours = parseInt(match[4], 10); + var minutes = parseInt(match[5], 10); + var secFrac = parseFloat(match[6]); + var seconds = secFrac | 0; + var milliseconds = Math.round(1000 * (secFrac - seconds)); + date.setUTCHours(hours, minutes, seconds, milliseconds); + // check offset + if (type !== "Z") { + var hoursOffset = parseInt(type, 10); + var minutesOffset = parseInt(match[8], 10) || 0; + var offset = -1000 * (60 * (hoursOffset * 60) + minutesOffset * 60); + date = new Date(date.getTime() + offset); + } + } + } else { + date = new Date("invalid"); + } + return date; + } +}; +/* ====================================================================== + OpenLayers/Format/Filter/v1.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ +/** + * @requires OpenLayers/Format/Filter.js + * @requires OpenLayers/Format/XML.js + * @requires OpenLayers/Filter/Function.js + * @requires OpenLayers/BaseTypes/Date.js + */ + +/** + * Class: OpenLayers.Format.Filter.v1 + * Superclass for Filter version 1 parsers. + * + * Inherits from: + * - + */ +OpenLayers.Format.Filter.v1 = OpenLayers.Class(OpenLayers.Format.XML, { + + /** + * Property: namespaces + * {Object} Mapping of namespace aliases to namespace URIs. + */ + namespaces: { + ogc: "http://www.opengis.net/ogc", + gml: "http://www.opengis.net/gml", + xlink: "http://www.w3.org/1999/xlink", + xsi: "http://www.w3.org/2001/XMLSchema-instance" + }, + + /** + * Property: defaultPrefix + */ + defaultPrefix: "ogc", + + /** + * Property: schemaLocation + * {String} Schema location for a particular minor version. + */ + schemaLocation: null, + + /** + * Constructor: OpenLayers.Format.Filter.v1 + * Instances of this class are not created directly. Use the + * constructor instead. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + initialize: function(options) { + OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); + }, + + /** + * Method: read + * + * Parameters: + * data - {DOMElement} A Filter document element. + * + * Returns: + * {} A filter object. + */ + read: function(data) { + var obj = {}; + this.readers.ogc["Filter"].apply(this, [data, obj]); + return obj.filter; + }, + + /** + * Property: readers + * Contains public functions, grouped by namespace prefix, that will + * be applied when a namespaced node is found matching the function + * name. The function will be applied in the scope of this parser + * with two arguments: the node being read and a context object passed + * from the parent. + */ + readers: { + "ogc": { + "_expression": function(node) { + // only the simplest of ogc:expression handled + // "some text and an attribute"} + var obj, value = ""; + for(var child=node.firstChild; child; child=child.nextSibling) { + switch(child.nodeType) { + case 1: + obj = this.readNode(child); + if (obj.property) { + value += "${" + obj.property + "}"; + } else if (obj.value !== undefined) { + value += obj.value; + } + break; + case 3: // text node + case 4: // cdata section + value += child.nodeValue; + } + } + return value; + }, + "Filter": function(node, parent) { + // Filters correspond to subclasses of OpenLayers.Filter. + // Since they contain information we don't persist, we + // create a temporary object and then pass on the filter + // (ogc:Filter) to the parent obj. + var obj = { + fids: [], + filters: [] + }; + this.readChildNodes(node, obj); + if(obj.fids.length > 0) { + parent.filter = new OpenLayers.Filter.FeatureId({ + fids: obj.fids + }); + } else if(obj.filters.length > 0) { + parent.filter = obj.filters[0]; + } + }, + "FeatureId": function(node, obj) { + var fid = node.getAttribute("fid"); + if(fid) { + obj.fids.push(fid); + } + }, + "And": function(node, obj) { + var filter = new OpenLayers.Filter.Logical({ + type: OpenLayers.Filter.Logical.AND + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "Or": function(node, obj) { + var filter = new OpenLayers.Filter.Logical({ + type: OpenLayers.Filter.Logical.OR + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "Not": function(node, obj) { + var filter = new OpenLayers.Filter.Logical({ + type: OpenLayers.Filter.Logical.NOT + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "PropertyIsLessThan": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.LESS_THAN + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "PropertyIsGreaterThan": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.GREATER_THAN + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "PropertyIsLessThanOrEqualTo": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "PropertyIsGreaterThanOrEqualTo": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "PropertyIsBetween": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.BETWEEN + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "Literal": function(node, obj) { + obj.value = OpenLayers.String.numericIf( + this.getChildValue(node), true); + }, + "PropertyName": function(node, filter) { + filter.property = this.getChildValue(node); + }, + "LowerBoundary": function(node, filter) { + filter.lowerBoundary = OpenLayers.String.numericIf( + this.readers.ogc._expression.call(this, node), true); + }, + "UpperBoundary": function(node, filter) { + filter.upperBoundary = OpenLayers.String.numericIf( + this.readers.ogc._expression.call(this, node), true); + }, + "Intersects": function(node, obj) { + this.readSpatial(node, obj, OpenLayers.Filter.Spatial.INTERSECTS); + }, + "Within": function(node, obj) { + this.readSpatial(node, obj, OpenLayers.Filter.Spatial.WITHIN); + }, + "Contains": function(node, obj) { + this.readSpatial(node, obj, OpenLayers.Filter.Spatial.CONTAINS); + }, + "DWithin": function(node, obj) { + this.readSpatial(node, obj, OpenLayers.Filter.Spatial.DWITHIN); + }, + "Distance": function(node, obj) { + obj.distance = parseInt(this.getChildValue(node)); + obj.distanceUnits = node.getAttribute("units"); + }, + "Function": function(node, obj) { + //TODO write decoder for it + return; + }, + "PropertyIsNull": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.IS_NULL + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + } + } + }, + + /** + * Method: readSpatial + * + * Read a {} filter. + * + * Parameters: + * node - {DOMElement} A DOM element that contains an ogc:expression. + * obj - {Object} The target object. + * type - {String} One of the OpenLayers.Filter.Spatial.* constants. + * + * Returns: + * {} The created filter. + */ + readSpatial: function(node, obj, type) { + var filter = new OpenLayers.Filter.Spatial({ + type: type + }); + this.readChildNodes(node, filter); + filter.value = filter.components[0]; + delete filter.components; + obj.filters.push(filter); + }, + + /** + * APIMethod: encodeLiteral + * Generates the string representation of a value for use in + * elements. The default encoder writes Date values as ISO 8601 + * strings. + * + * Parameters: + * value - {Object} Literal value to encode + * + * Returns: + * {String} String representation of the provided value. + */ + encodeLiteral: function(value) { + if (value instanceof Date) { + value = OpenLayers.Date.toISOString(value); + } + return value; + }, + + /** + * Method: writeOgcExpression + * Limited support for writing OGC expressions. Currently it supports + * ( || String || Number) + * + * Parameters: + * value - ( || String || Number) + * node - {DOMElement} A parent DOM element + * + * Returns: + * {DOMElement} Updated node element. + */ + writeOgcExpression: function(value, node) { + if (value instanceof OpenLayers.Filter.Function){ + this.writeNode("Function", value, node); + } else { + this.writeNode("Literal", value, node); + } + return node; + }, + + /** + * Method: write + * + * Parameters: + * filter - {} A filter object. + * + * Returns: + * {DOMElement} An ogc:Filter element. + */ + write: function(filter) { + return this.writers.ogc["Filter"].apply(this, [filter]); + }, + + /** + * Property: writers + * As a compliment to the readers property, this structure contains public + * writing functions grouped by namespace alias and named like the + * node names they produce. + */ + writers: { + "ogc": { + "Filter": function(filter) { + var node = this.createElementNSPlus("ogc:Filter"); + this.writeNode(this.getFilterType(filter), filter, node); + return node; + }, + "_featureIds": function(filter) { + var node = this.createDocumentFragment(); + for (var i=0, ii=filter.fids.length; i": "PropertyIsGreaterThan", + "<=": "PropertyIsLessThanOrEqualTo", + ">=": "PropertyIsGreaterThanOrEqualTo", + "..": "PropertyIsBetween", + "~": "PropertyIsLike", + "NULL": "PropertyIsNull", + "BBOX": "BBOX", + "DWITHIN": "DWITHIN", + "WITHIN": "WITHIN", + "CONTAINS": "CONTAINS", + "INTERSECTS": "INTERSECTS", + "FID": "_featureIds" + }, + + CLASS_NAME: "OpenLayers.Format.Filter.v1" + +}); +/* ====================================================================== + OpenLayers/Format/Filter/v1_0_0.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Format/GML/v2.js + * @requires OpenLayers/Format/Filter/v1.js + */ + +/** + * Class: OpenLayers.Format.Filter.v1_0_0 + * Write ogc:Filter version 1.0.0. + * + * Inherits from: + * - + * - + */ +OpenLayers.Format.Filter.v1_0_0 = OpenLayers.Class( + OpenLayers.Format.GML.v2, OpenLayers.Format.Filter.v1, { + + /** + * Constant: VERSION + * {String} 1.0.0 + */ + VERSION: "1.0.0", + + /** + * Property: schemaLocation + * {String} http://www.opengis.net/ogc/filter/1.0.0/filter.xsd + */ + schemaLocation: "http://www.opengis.net/ogc/filter/1.0.0/filter.xsd", + + /** + * Constructor: OpenLayers.Format.Filter.v1_0_0 + * Instances of this class are not created directly. Use the + * constructor instead. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + initialize: function(options) { + OpenLayers.Format.GML.v2.prototype.initialize.apply( + this, [options] + ); + }, + + /** + * Property: readers + * Contains public functions, grouped by namespace prefix, that will + * be applied when a namespaced node is found matching the function + * name. The function will be applied in the scope of this parser + * with two arguments: the node being read and a context object passed + * from the parent. + */ + readers: { + "ogc": OpenLayers.Util.applyDefaults({ + "PropertyIsEqualTo": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.EQUAL_TO + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "PropertyIsNotEqualTo": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "PropertyIsLike": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.LIKE + }); + this.readChildNodes(node, filter); + var wildCard = node.getAttribute("wildCard"); + var singleChar = node.getAttribute("singleChar"); + var esc = node.getAttribute("escape"); + filter.value2regex(wildCard, singleChar, esc); + obj.filters.push(filter); + } + }, OpenLayers.Format.Filter.v1.prototype.readers["ogc"]), + "gml": OpenLayers.Format.GML.v2.prototype.readers["gml"], + "feature": OpenLayers.Format.GML.v2.prototype.readers["feature"] + }, + + /** + * Property: writers + * As a compliment to the readers property, this structure contains public + * writing functions grouped by namespace alias and named like the + * node names they produce. + */ + writers: { + "ogc": OpenLayers.Util.applyDefaults({ + "PropertyIsEqualTo": function(filter) { + var node = this.createElementNSPlus("ogc:PropertyIsEqualTo"); + // no ogc:expression handling for PropertyName for now + this.writeNode("PropertyName", filter, node); + // handle Literals or Functions for now + this.writeOgcExpression(filter.value, node); + return node; + }, + "PropertyIsNotEqualTo": function(filter) { + var node = this.createElementNSPlus("ogc:PropertyIsNotEqualTo"); + // no ogc:expression handling for PropertyName for now + this.writeNode("PropertyName", filter, node); + // handle Literals or Functions for now + this.writeOgcExpression(filter.value, node); + return node; + }, + "PropertyIsLike": function(filter) { + var node = this.createElementNSPlus("ogc:PropertyIsLike", { + attributes: { + wildCard: "*", singleChar: ".", escape: "!" + } + }); + // no ogc:expression handling for now + this.writeNode("PropertyName", filter, node); + // convert regex string to ogc string + this.writeNode("Literal", filter.regex2value(), node); + return node; + }, + "BBOX": function(filter) { + var node = this.createElementNSPlus("ogc:BBOX"); + // PropertyName is mandatory in 1.0.0, but e.g. GeoServer also + // accepts filters without it. When this is used with + // OpenLayers.Protocol.WFS, OpenLayers.Format.WFST will set a + // missing filter.property to the geometryName that is + // configured with the protocol, which defaults to "the_geom". + // So the only way to omit this mandatory property is to not + // set the property on the filter and to set the geometryName + // on the WFS protocol to null. The latter also happens when + // the protocol is configured without a geometryName and a + // featureNS. + filter.property && this.writeNode("PropertyName", filter, node); + var box = this.writeNode("gml:Box", filter.value, node); + if(filter.projection) { + box.setAttribute("srsName", filter.projection); + } + return node; + } + }, OpenLayers.Format.Filter.v1.prototype.writers["ogc"]), + "gml": OpenLayers.Format.GML.v2.prototype.writers["gml"], + "feature": OpenLayers.Format.GML.v2.prototype.writers["feature"] + }, + + /** + * Method: writeSpatial + * + * Read a {} filter and converts it into XML. + * + * Parameters: + * filter - {} The filter. + * name - {String} Name of the generated XML element. + * + * Returns: + * {DOMElement} The created XML element. + */ + writeSpatial: function(filter, name) { + var node = this.createElementNSPlus("ogc:"+name); + this.writeNode("PropertyName", filter, node); + if(filter.value instanceof OpenLayers.Filter.Function) { + this.writeNode("Function", filter.value, node); + } else { + var child; + if(filter.value instanceof OpenLayers.Geometry) { + child = this.writeNode("feature:_geometry", filter.value).firstChild; + } else { + child = this.writeNode("gml:Box", filter.value); + } + if(filter.projection) { + child.setAttribute("srsName", filter.projection); + } + node.appendChild(child); + } + return node; + }, + + + CLASS_NAME: "OpenLayers.Format.Filter.v1_0_0" + +}); +/* ====================================================================== + OpenLayers/Format/WFST/v1_0_0.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Format/WFST/v1.js + * @requires OpenLayers/Format/Filter/v1_0_0.js + */ + +/** + * Class: OpenLayers.Format.WFST.v1_0_0 + * A format for creating WFS v1.0.0 transactions. Create a new instance with the + * constructor. + * + * Inherits from: + * - + * - + */ +OpenLayers.Format.WFST.v1_0_0 = OpenLayers.Class( + OpenLayers.Format.Filter.v1_0_0, OpenLayers.Format.WFST.v1, { + + /** + * Property: version + * {String} WFS version number. + */ + version: "1.0.0", + + /** + * APIProperty: srsNameInQuery + * {Boolean} If true the reference system is passed in Query requests + * via the "srsName" attribute to the "wfs:Query" element, this + * property defaults to false as it isn't WFS 1.0.0 compliant. + */ + srsNameInQuery: false, + + /** + * Property: schemaLocations + * {Object} Properties are namespace aliases, values are schema locations. + */ + schemaLocations: { + "wfs": "http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd" + }, + + /** + * Constructor: OpenLayers.Format.WFST.v1_0_0 + * A class for parsing and generating WFS v1.0.0 transactions. + * + * Parameters: + * options - {Object} Optional object whose properties will be set on the + * instance. + * + * Valid options properties: + * featureType - {String} Local (without prefix) feature typeName (required). + * featureNS - {String} Feature namespace (optional). + * featurePrefix - {String} Feature namespace alias (optional - only used + * if featureNS is provided). Default is 'feature'. + * geometryName - {String} Name of geometry attribute. Default is 'the_geom'. + */ + initialize: function(options) { + OpenLayers.Format.Filter.v1_0_0.prototype.initialize.apply(this, [options]); + OpenLayers.Format.WFST.v1.prototype.initialize.apply(this, [options]); + }, + + /** + * Method: readNode + * Shorthand for applying one of the named readers given the node + * namespace and local name. Readers take two args (node, obj) and + * generally extend or modify the second. + * + * Parameters: + * node - {DOMElement} The node to be read (required). + * obj - {Object} The object to be modified (optional). + * first - {Boolean} Should be set to true for the first node read. This + * is usually the readNode call in the read method. Without this being + * set, auto-configured properties will stick on subsequent reads. + * + * Returns: + * {Object} The input object, modified (or a new one if none was provided). + */ + readNode: function(node, obj, first) { + // Not the superclass, only the mixin classes inherit from + // Format.GML.v2. We need this because we don't want to get readNode + // from the superclass's superclass, which is OpenLayers.Format.XML. + return OpenLayers.Format.GML.v2.prototype.readNode.apply(this, arguments); + }, + + /** + * Property: readers + * Contains public functions, grouped by namespace prefix, that will + * be applied when a namespaced node is found matching the function + * name. The function will be applied in the scope of this parser + * with two arguments: the node being read and a context object passed + * from the parent. + */ + readers: { + "wfs": OpenLayers.Util.applyDefaults({ + "WFS_TransactionResponse": function(node, obj) { + obj.insertIds = []; + obj.success = false; + this.readChildNodes(node, obj); + }, + "InsertResult": function(node, container) { + var obj = {fids: []}; + this.readChildNodes(node, obj); + container.insertIds = container.insertIds.concat(obj.fids); + }, + "TransactionResult": function(node, obj) { + this.readChildNodes(node, obj); + }, + "Status": function(node, obj) { + this.readChildNodes(node, obj); + }, + "SUCCESS": function(node, obj) { + obj.success = true; + } + }, OpenLayers.Format.WFST.v1.prototype.readers["wfs"]), + "gml": OpenLayers.Format.GML.v2.prototype.readers["gml"], + "feature": OpenLayers.Format.GML.v2.prototype.readers["feature"], + "ogc": OpenLayers.Format.Filter.v1_0_0.prototype.readers["ogc"] + }, + + /** + * Property: writers + * As a compliment to the readers property, this structure contains public + * writing functions grouped by namespace alias and named like the + * node names they produce. + */ + writers: { + "wfs": OpenLayers.Util.applyDefaults({ + "Query": function(options) { + options = OpenLayers.Util.extend({ + featureNS: this.featureNS, + featurePrefix: this.featurePrefix, + featureType: this.featureType, + srsName: this.srsName, + srsNameInQuery: this.srsNameInQuery + }, options); + var prefix = options.featurePrefix; + var node = this.createElementNSPlus("wfs:Query", { + attributes: { + typeName: (prefix ? prefix + ":" : "") + + options.featureType + } + }); + if(options.srsNameInQuery && options.srsName) { + node.setAttribute("srsName", options.srsName); + } + if(options.featureNS) { + node.setAttribute("xmlns:" + prefix, options.featureNS); + } + if(options.propertyNames) { + for(var i=0,len = options.propertyNames.length; i} This is an array of node id's stored in the + * order that they should show up on screen. Id's higher up in the + * array (higher array index) represent nodes with higher z-indeces. + */ + order: null, + + /** + * Property: indices + * {Object} This is a hash that maps node ids to their z-index value + * stored in the indexer. This is done to make finding a nodes z-index + * value O(1). + */ + indices: null, + + /** + * Property: compare + * {Function} This is the function used to determine placement of + * of a new node within the indexer. If null, this defaults to to + * the Z_ORDER_DRAWING_ORDER comparison method. + */ + compare: null, + + /** + * APIMethod: initialize + * Create a new indexer with + * + * Parameters: + * yOrdering - {Boolean} Whether to use y-ordering. + */ + initialize: function(yOrdering) { + + this.compare = yOrdering ? + OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_Y_ORDER : + OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_DRAWING_ORDER; + + this.clear(); + }, + + /** + * APIMethod: insert + * Insert a new node into the indexer. In order to find the correct + * positioning for the node to be inserted, this method uses a binary + * search. This makes inserting O(log(n)). + * + * Parameters: + * newNode - {DOMElement} The new node to be inserted. + * + * Returns + * {DOMElement} the node before which we should insert our newNode, or + * null if newNode can just be appended. + */ + insert: function(newNode) { + // If the node is known to the indexer, remove it so we can + // recalculate where it should go. + if (this.exists(newNode)) { + this.remove(newNode); + } + + var nodeId = newNode.id; + + this.determineZIndex(newNode); + + var leftIndex = -1; + var rightIndex = this.order.length; + var middle; + + while (rightIndex - leftIndex > 1) { + middle = parseInt((leftIndex + rightIndex) / 2); + + var placement = this.compare(this, newNode, + OpenLayers.Util.getElement(this.order[middle])); + + if (placement > 0) { + leftIndex = middle; + } else { + rightIndex = middle; + } + } + + this.order.splice(rightIndex, 0, nodeId); + this.indices[nodeId] = this.getZIndex(newNode); + + // If the new node should be before another in the index + // order, return the node before which we have to insert the new one; + // else, return null to indicate that the new node can be appended. + return this.getNextElement(rightIndex); + }, + + /** + * APIMethod: remove + * + * Parameters: + * node - {DOMElement} The node to be removed. + */ + remove: function(node) { + var nodeId = node.id; + var arrayIndex = OpenLayers.Util.indexOf(this.order, nodeId); + if (arrayIndex >= 0) { + // Remove it from the order array, as well as deleting the node + // from the indeces hash. + this.order.splice(arrayIndex, 1); + delete this.indices[nodeId]; + + // Reset the maxium z-index based on the last item in the + // order array. + if (this.order.length > 0) { + var lastId = this.order[this.order.length - 1]; + this.maxZIndex = this.indices[lastId]; + } else { + this.maxZIndex = 0; + } + } + }, + + /** + * APIMethod: clear + */ + clear: function() { + this.order = []; + this.indices = {}; + this.maxZIndex = 0; + }, + + /** + * APIMethod: exists + * + * Parameters: + * node - {DOMElement} The node to test for existence. + * + * Returns: + * {Boolean} Whether or not the node exists in the indexer? + */ + exists: function(node) { + return (this.indices[node.id] != null); + }, + + /** + * APIMethod: getZIndex + * Get the z-index value for the current node from the node data itself. + * + * Parameters: + * node - {DOMElement} The node whose z-index to get. + * + * Returns: + * {Integer} The z-index value for the specified node (from the node + * data itself). + */ + getZIndex: function(node) { + return node._style.graphicZIndex; + }, + + /** + * Method: determineZIndex + * Determine the z-index for the current node if there isn't one, + * and set the maximum value if we've found a new maximum. + * + * Parameters: + * node - {DOMElement} + */ + determineZIndex: function(node) { + var zIndex = node._style.graphicZIndex; + + // Everything must have a zIndex. If none is specified, + // this means the user *must* (hint: assumption) want this + // node to succomb to drawing order. To enforce drawing order + // over all indexing methods, we'll create a new z-index that's + // greater than any currently in the indexer. + if (zIndex == null) { + zIndex = this.maxZIndex; + node._style.graphicZIndex = zIndex; + } else if (zIndex > this.maxZIndex) { + this.maxZIndex = zIndex; + } + }, + + /** + * APIMethod: getNextElement + * Get the next element in the order stack. + * + * Parameters: + * index - {Integer} The index of the current node in this.order. + * + * Returns: + * {DOMElement} the node following the index passed in, or + * null. + */ + getNextElement: function(index) { + var nextIndex = index + 1; + if (nextIndex < this.order.length) { + var nextElement = OpenLayers.Util.getElement(this.order[nextIndex]); + if (nextElement == undefined) { + nextElement = this.getNextElement(nextIndex); + } + return nextElement; + } else { + return null; + } + }, + + CLASS_NAME: "OpenLayers.ElementsIndexer" +}); + +/** + * Namespace: OpenLayers.ElementsIndexer.IndexingMethods + * These are the compare methods for figuring out where a new node should be + * placed within the indexer. These methods are very similar to general + * sorting methods in that they return -1, 0, and 1 to specify the + * direction in which new nodes fall in the ordering. + */ +OpenLayers.ElementsIndexer.IndexingMethods = { + + /** + * Method: Z_ORDER + * This compare method is used by other comparison methods. + * It can be used individually for ordering, but is not recommended, + * because it doesn't subscribe to drawing order. + * + * Parameters: + * indexer - {} + * newNode - {DOMElement} + * nextNode - {DOMElement} + * + * Returns: + * {Integer} + */ + Z_ORDER: function(indexer, newNode, nextNode) { + var newZIndex = indexer.getZIndex(newNode); + + var returnVal = 0; + if (nextNode) { + var nextZIndex = indexer.getZIndex(nextNode); + returnVal = newZIndex - nextZIndex; + } + + return returnVal; + }, + + /** + * APIMethod: Z_ORDER_DRAWING_ORDER + * This method orders nodes by their z-index, but does so in a way + * that, if there are other nodes with the same z-index, the newest + * drawn will be the front most within that z-index. This is the + * default indexing method. + * + * Parameters: + * indexer - {} + * newNode - {DOMElement} + * nextNode - {DOMElement} + * + * Returns: + * {Integer} + */ + Z_ORDER_DRAWING_ORDER: function(indexer, newNode, nextNode) { + var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER( + indexer, + newNode, + nextNode + ); + + // Make Z_ORDER subscribe to drawing order by pushing it above + // all of the other nodes with the same z-index. + if (nextNode && returnVal == 0) { + returnVal = 1; + } + + return returnVal; + }, + + /** + * APIMethod: Z_ORDER_Y_ORDER + * This one should really be called Z_ORDER_Y_ORDER_DRAWING_ORDER, as it + * best describes which ordering methods have precedence (though, the + * name would be too long). This method orders nodes by their z-index, + * but does so in a way that, if there are other nodes with the same + * z-index, the nodes with the lower y position will be "closer" than + * those with a higher y position. If two nodes have the exact same y + * position, however, then this method will revert to using drawing + * order to decide placement. + * + * Parameters: + * indexer - {} + * newNode - {DOMElement} + * nextNode - {DOMElement} + * + * Returns: + * {Integer} + */ + Z_ORDER_Y_ORDER: function(indexer, newNode, nextNode) { + var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER( + indexer, + newNode, + nextNode + ); + + if (nextNode && returnVal === 0) { + var result = nextNode._boundsBottom - newNode._boundsBottom; + returnVal = (result === 0) ? 1 : result; + } + + return returnVal; + } +}; + +/** + * Class: OpenLayers.Renderer.Elements + * This is another virtual class in that it should never be instantiated by + * itself as a Renderer. It exists because there is *tons* of shared + * functionality between different vector libraries which use nodes/elements + * as a base for rendering vectors. + * + * The highlevel bits of code that are implemented here are the adding and + * removing of geometries, which is essentially the same for any + * element-based renderer. The details of creating each node and drawing the + * paths are of course different, but the machinery is the same. + * + * Inherits: + * - + */ +OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, { + + /** + * Property: rendererRoot + * {DOMElement} + */ + rendererRoot: null, + + /** + * Property: root + * {DOMElement} + */ + root: null, + + /** + * Property: vectorRoot + * {DOMElement} + */ + vectorRoot: null, + + /** + * Property: textRoot + * {DOMElement} + */ + textRoot: null, + + /** + * Property: xmlns + * {String} + */ + xmlns: null, + + /** + * Property: xOffset + * {Number} Offset to apply to the renderer viewport translation in x + * direction. If the renderer extent's center is on the right of the + * dateline (i.e. exceeds the world bounds), we shift the viewport to the + * left by one world width. This avoids that features disappear from the + * map viewport. Because our dateline handling logic in other places + * ensures that extents crossing the dateline always have a center + * exceeding the world bounds on the left, we need this offset to make sure + * that the same is true for the renderer extent in pixel space as well. + */ + xOffset: 0, + + /** + * Property: rightOfDateLine + * {Boolean} Keeps track of the location of the map extent relative to the + * date line. The method compares this value (which is the one + * from the previous call) with the current position of the map + * extent relative to the date line and updates the xOffset when the extent + * has moved from one side of the date line to the other. + */ + + /** + * Property: Indexer + * {} An instance of OpenLayers.ElementsIndexer + * created upon initialization if the zIndexing or yOrdering options + * passed to this renderer's constructor are set to true. + */ + indexer: null, + + /** + * Constant: BACKGROUND_ID_SUFFIX + * {String} + */ + BACKGROUND_ID_SUFFIX: "_background", + + /** + * Constant: LABEL_ID_SUFFIX + * {String} + */ + LABEL_ID_SUFFIX: "_label", + + /** + * Constant: LABEL_OUTLINE_SUFFIX + * {String} + */ + LABEL_OUTLINE_SUFFIX: "_outline", + + /** + * Constructor: OpenLayers.Renderer.Elements + * + * Parameters: + * containerID - {String} + * options - {Object} options for this renderer. + * + * Supported options are: + * yOrdering - {Boolean} Whether to use y-ordering + * zIndexing - {Boolean} Whether to use z-indexing. Will be ignored + * if yOrdering is set to true. + */ + initialize: function(containerID, options) { + OpenLayers.Renderer.prototype.initialize.apply(this, arguments); + + this.rendererRoot = this.createRenderRoot(); + this.root = this.createRoot("_root"); + this.vectorRoot = this.createRoot("_vroot"); + this.textRoot = this.createRoot("_troot"); + + this.root.appendChild(this.vectorRoot); + this.root.appendChild(this.textRoot); + + this.rendererRoot.appendChild(this.root); + this.container.appendChild(this.rendererRoot); + + if(options && (options.zIndexing || options.yOrdering)) { + this.indexer = new OpenLayers.ElementsIndexer(options.yOrdering); + } + }, + + /** + * Method: destroy + */ + destroy: function() { + + this.clear(); + + this.rendererRoot = null; + this.root = null; + this.xmlns = null; + + OpenLayers.Renderer.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: clear + * Remove all the elements from the root + */ + clear: function() { + var child; + var root = this.vectorRoot; + if (root) { + while (child = root.firstChild) { + root.removeChild(child); + } + } + root = this.textRoot; + if (root) { + while (child = root.firstChild) { + root.removeChild(child); + } + } + if (this.indexer) { + this.indexer.clear(); + } + }, + + /** + * Method: setExtent + * Set the visible part of the layer. + * + * Parameters: + * extent - {} + * resolutionChanged - {Boolean} + * + * Returns: + * {Boolean} true to notify the layer that the new extent does not exceed + * the coordinate range, and the features will not need to be redrawn. + * False otherwise. + */ + setExtent: function(extent, resolutionChanged) { + var coordSysUnchanged = OpenLayers.Renderer.prototype.setExtent.apply(this, arguments); + var resolution = this.getResolution(); + if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) { + var rightOfDateLine, + ratio = extent.getWidth() / this.map.getExtent().getWidth(), + extent = extent.scale(1 / ratio), + world = this.map.getMaxExtent(); + if (world.right > extent.left && world.right < extent.right) { + rightOfDateLine = true; + } else if (world.left > extent.left && world.left < extent.right) { + rightOfDateLine = false; + } + if (rightOfDateLine !== this.rightOfDateLine || resolutionChanged) { + coordSysUnchanged = false; + this.xOffset = rightOfDateLine === true ? + world.getWidth() / resolution : 0; + } + this.rightOfDateLine = rightOfDateLine; + } + return coordSysUnchanged; + }, + + /** + * Method: getNodeType + * This function is in charge of asking the specific renderer which type + * of node to create for the given geometry and style. All geometries + * in an Elements-based renderer consist of one node and some + * attributes. We have the nodeFactory() function which creates a node + * for us, but it takes a 'type' as input, and that is precisely what + * this function tells us. + * + * Parameters: + * geometry - {} + * style - {Object} + * + * Returns: + * {String} The corresponding node type for the specified geometry + */ + getNodeType: function(geometry, style) { }, + + /** + * Method: drawGeometry + * Draw the geometry, creating new nodes, setting paths, setting style, + * setting featureId on the node. This method should only be called + * by the renderer itself. + * + * Parameters: + * geometry - {} + * style - {Object} + * featureId - {String} + * + * Returns: + * {Boolean} true if the geometry has been drawn completely; null if + * incomplete; false otherwise + */ + drawGeometry: function(geometry, style, featureId) { + var className = geometry.CLASS_NAME; + var rendered = true; + if ((className == "OpenLayers.Geometry.Collection") || + (className == "OpenLayers.Geometry.MultiPoint") || + (className == "OpenLayers.Geometry.MultiLineString") || + (className == "OpenLayers.Geometry.MultiPolygon")) { + for (var i = 0, len=geometry.components.length; i} + * style - {Object} + * featureId - {String} + * + * Returns: + * {Boolean} true if the complete geometry could be drawn, null if parts of + * the geometry could not be drawn, false otherwise + */ + redrawNode: function(id, geometry, style, featureId) { + style = this.applyDefaultSymbolizer(style); + // Get the node if it's already on the map. + var node = this.nodeFactory(id, this.getNodeType(geometry, style)); + + // Set the data for the node, then draw it. + node._featureId = featureId; + node._boundsBottom = geometry.getBounds().bottom; + node._geometryClass = geometry.CLASS_NAME; + node._style = style; + + var drawResult = this.drawGeometryNode(node, geometry, style); + if(drawResult === false) { + return false; + } + + node = drawResult.node; + + // Insert the node into the indexer so it can show us where to + // place it. Note that this operation is O(log(n)). If there's a + // performance problem (when dragging, for instance) this is + // likely where it would be. + if (this.indexer) { + var insert = this.indexer.insert(node); + if (insert) { + this.vectorRoot.insertBefore(node, insert); + } else { + this.vectorRoot.appendChild(node); + } + } else { + // if there's no indexer, simply append the node to root, + // but only if the node is a new one + if (node.parentNode !== this.vectorRoot){ + this.vectorRoot.appendChild(node); + } + } + + this.postDraw(node); + + return drawResult.complete; + }, + + /** + * Method: redrawBackgroundNode + * Redraws the node using special 'background' style properties. Basically + * just calls redrawNode(), but instead of directly using the + * 'externalGraphic', 'graphicXOffset', 'graphicYOffset', and + * 'graphicZIndex' properties directly from the specified 'style' + * parameter, we create a new style object and set those properties + * from the corresponding 'background'-prefixed properties from + * specified 'style' parameter. + * + * Parameters: + * id - {String} + * geometry - {} + * style - {Object} + * featureId - {String} + * + * Returns: + * {Boolean} true if the complete geometry could be drawn, null if parts of + * the geometry could not be drawn, false otherwise + */ + redrawBackgroundNode: function(id, geometry, style, featureId) { + var backgroundStyle = OpenLayers.Util.extend({}, style); + + // Set regular style attributes to apply to the background styles. + backgroundStyle.externalGraphic = backgroundStyle.backgroundGraphic; + backgroundStyle.graphicXOffset = backgroundStyle.backgroundXOffset; + backgroundStyle.graphicYOffset = backgroundStyle.backgroundYOffset; + backgroundStyle.graphicZIndex = backgroundStyle.backgroundGraphicZIndex; + backgroundStyle.graphicWidth = backgroundStyle.backgroundWidth || backgroundStyle.graphicWidth; + backgroundStyle.graphicHeight = backgroundStyle.backgroundHeight || backgroundStyle.graphicHeight; + + // Erase background styles. + backgroundStyle.backgroundGraphic = null; + backgroundStyle.backgroundXOffset = null; + backgroundStyle.backgroundYOffset = null; + backgroundStyle.backgroundGraphicZIndex = null; + + return this.redrawNode( + id + this.BACKGROUND_ID_SUFFIX, + geometry, + backgroundStyle, + null + ); + }, + + /** + * Method: drawGeometryNode + * Given a node, draw a geometry on the specified layer. + * node and geometry are required arguments, style is optional. + * This method is only called by the render itself. + * + * Parameters: + * node - {DOMElement} + * geometry - {} + * style - {Object} + * + * Returns: + * {Object} a hash with properties "node" (the drawn node) and "complete" + * (null if parts of the geometry could not be drawn, false if nothing + * could be drawn) + */ + drawGeometryNode: function(node, geometry, style) { + style = style || node._style; + + var options = { + 'isFilled': style.fill === undefined ? + true : + style.fill, + 'isStroked': style.stroke === undefined ? + !!style.strokeWidth : + style.stroke + }; + var drawn; + switch (geometry.CLASS_NAME) { + case "OpenLayers.Geometry.Point": + if(style.graphic === false) { + options.isFilled = false; + options.isStroked = false; + } + drawn = this.drawPoint(node, geometry); + break; + case "OpenLayers.Geometry.LineString": + options.isFilled = false; + drawn = this.drawLineString(node, geometry); + break; + case "OpenLayers.Geometry.LinearRing": + drawn = this.drawLinearRing(node, geometry); + break; + case "OpenLayers.Geometry.Polygon": + drawn = this.drawPolygon(node, geometry); + break; + case "OpenLayers.Geometry.Rectangle": + drawn = this.drawRectangle(node, geometry); + break; + default: + break; + } + + node._options = options; + + //set style + //TBD simplify this + if (drawn != false) { + return { + node: this.setStyle(node, style, options, geometry), + complete: drawn + }; + } else { + return false; + } + }, + + /** + * Method: postDraw + * Things that have do be done after the geometry node is appended + * to its parent node. To be overridden by subclasses. + * + * Parameters: + * node - {DOMElement} + */ + postDraw: function(node) {}, + + /** + * Method: drawPoint + * Virtual function for drawing Point Geometry. + * Should be implemented by subclasses. + * This method is only called by the renderer itself. + * + * Parameters: + * node - {DOMElement} + * geometry - {} + * + * Returns: + * {DOMElement} or false if the renderer could not draw the point + */ + drawPoint: function(node, geometry) {}, + + /** + * Method: drawLineString + * Virtual function for drawing LineString Geometry. + * Should be implemented by subclasses. + * This method is only called by the renderer itself. + * + * Parameters: + * node - {DOMElement} + * geometry - {} + * + * Returns: + * {DOMElement} or null if the renderer could not draw all components of + * the linestring, or false if nothing could be drawn + */ + drawLineString: function(node, geometry) {}, + + /** + * Method: drawLinearRing + * Virtual function for drawing LinearRing Geometry. + * Should be implemented by subclasses. + * This method is only called by the renderer itself. + * + * Parameters: + * node - {DOMElement} + * geometry - {} + * + * Returns: + * {DOMElement} or null if the renderer could not draw all components + * of the linear ring, or false if nothing could be drawn + */ + drawLinearRing: function(node, geometry) {}, + + /** + * Method: drawPolygon + * Virtual function for drawing Polygon Geometry. + * Should be implemented by subclasses. + * This method is only called by the renderer itself. + * + * Parameters: + * node - {DOMElement} + * geometry - {} + * + * Returns: + * {DOMElement} or null if the renderer could not draw all components + * of the polygon, or false if nothing could be drawn + */ + drawPolygon: function(node, geometry) {}, + + /** + * Method: drawRectangle + * Virtual function for drawing Rectangle Geometry. + * Should be implemented by subclasses. + * This method is only called by the renderer itself. + * + * Parameters: + * node - {DOMElement} + * geometry - {} + * + * Returns: + * {DOMElement} or false if the renderer could not draw the rectangle + */ + drawRectangle: function(node, geometry) {}, + + /** + * Method: drawCircle + * Virtual function for drawing Circle Geometry. + * Should be implemented by subclasses. + * This method is only called by the renderer itself. + * + * Parameters: + * node - {DOMElement} + * geometry - {} + * + * Returns: + * {DOMElement} or false if the renderer could not draw the circle + */ + drawCircle: function(node, geometry) {}, + + /** + * Method: removeText + * Removes a label + * + * Parameters: + * featureId - {String} + */ + removeText: function(featureId) { + var label = document.getElementById(featureId + this.LABEL_ID_SUFFIX); + if (label) { + this.textRoot.removeChild(label); + } + var outline = document.getElementById(featureId + this.LABEL_OUTLINE_SUFFIX); + if (outline) { + this.textRoot.removeChild(outline); + } + }, + + /** + * Method: getFeatureIdFromEvent + * + * Parameters: + * evt - {Object} An object + * + * Returns: + * {String} A feature id or undefined. + */ + getFeatureIdFromEvent: function(evt) { + var target = evt.target; + var useElement = target && target.correspondingUseElement; + var node = useElement ? useElement : (target || evt.srcElement); + return node._featureId; + }, + + /** + * Method: eraseGeometry + * Erase a geometry from the renderer. In the case of a multi-geometry, + * we cycle through and recurse on ourselves. Otherwise, we look for a + * node with the geometry.id, destroy its geometry, and remove it from + * the DOM. + * + * Parameters: + * geometry - {} + * featureId - {String} + */ + eraseGeometry: function(geometry, featureId) { + if ((geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPoint") || + (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiLineString") || + (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPolygon") || + (geometry.CLASS_NAME == "OpenLayers.Geometry.Collection")) { + for (var i=0, len=geometry.components.length; i} target renderer for the moved root + */ + moveRoot: function(renderer) { + var root = this.root; + if(renderer.root.parentNode == this.rendererRoot) { + root = renderer.root; + } + root.parentNode.removeChild(root); + renderer.rendererRoot.appendChild(root); + }, + + /** + * Method: getRenderLayerId + * Gets the layer that this renderer's output appears on. If moveRoot was + * used, this will be different from the id of the layer containing the + * features rendered by this renderer. + * + * Returns: + * {String} the id of the output layer. + */ + getRenderLayerId: function() { + return this.root.parentNode.parentNode.id; + }, + + /** + * Method: isComplexSymbol + * Determines if a symbol cannot be rendered using drawCircle + * + * Parameters: + * graphicName - {String} + * + * Returns + * {Boolean} true if the symbol is complex, false if not + */ + isComplexSymbol: function(graphicName) { + return (graphicName != "circle") && !!graphicName; + }, + + CLASS_NAME: "OpenLayers.Renderer.Elements" +}); + +/* ====================================================================== + OpenLayers/Control/Panel.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Control.js + * @requires OpenLayers/Events/buttonclick.js + */ + +/** + * Class: OpenLayers.Control.Panel + * The Panel control is a container for other controls. With it toolbars + * may be composed. + * + * Inherits from: + * - + */ +OpenLayers.Control.Panel = OpenLayers.Class(OpenLayers.Control, { + /** + * Property: controls + * {Array()} + */ + controls: null, + + /** + * APIProperty: autoActivate + * {Boolean} Activate the control when it is added to a map. Default is + * true. + */ + autoActivate: true, + + /** + * APIProperty: defaultControl + * {} The control which is activated when the control is + * activated (turned on), which also happens at instantiation. + * If is true, will be nullified after the + * first activation of the panel. + */ + defaultControl: null, + + /** + * APIProperty: saveState + * {Boolean} If set to true, the active state of this panel's controls will + * be stored on panel deactivation, and restored on reactivation. Default + * is false. + */ + saveState: false, + + /** + * APIProperty: allowDepress + * {Boolean} If is true the controls can + * be deactivated by clicking the icon that represents them. Default + * is false. + */ + allowDepress: false, + + /** + * Property: activeState + * {Object} stores the active state of this panel's controls. + */ + activeState: null, + + /** + * Constructor: OpenLayers.Control.Panel + * Create a new control panel. + * + * Each control in the panel is represented by an icon. When clicking + * on an icon, the method is called. + * + * Specific properties for controls on a panel: + * type - {Number} One of , + * , . + * If not provided, is assumed. + * title - {string} Text displayed when mouse is over the icon that + * represents the control. + * + * The of a control determines the behavior when + * clicking its icon: + * - The control is activated and other + * controls of this type in the same panel are deactivated. This is + * the default type. + * - The active state of the control is + * toggled. + * - The + * method of the control is called, + * but its active state is not changed. + * + * If a control is , it will be drawn with the + * olControl[Name]ItemActive class, otherwise with the + * olControl[Name]ItemInactive class. + * + * Parameters: + * options - {Object} An optional object whose properties will be used + * to extend the control. + */ + initialize: function(options) { + OpenLayers.Control.prototype.initialize.apply(this, [options]); + this.controls = []; + this.activeState = {}; + }, + + /** + * APIMethod: destroy + */ + destroy: function() { + if (this.map) { + this.map.events.unregister("buttonclick", this, this.onButtonClick); + } + OpenLayers.Control.prototype.destroy.apply(this, arguments); + for (var ctl, i = this.controls.length - 1; i >= 0; i--) { + ctl = this.controls[i]; + if (ctl.events) { + ctl.events.un({ + activate: this.iconOn, + deactivate: this.iconOff + }); + } + ctl.panel_div = null; + } + this.activeState = null; + }, + + /** + * APIMethod: activate + */ + activate: function() { + if (OpenLayers.Control.prototype.activate.apply(this, arguments)) { + var control; + for (var i=0, len=this.controls.length; i=0; i--) { + this.div.removeChild(this.div.childNodes[i]); + } + this.div.innerHTML = ""; + if (this.active) { + for (var i=0, len=this.controls.length; i} + */ + activateControl: function (control) { + if (!this.active) { return false; } + if (control.type == OpenLayers.Control.TYPE_BUTTON) { + control.trigger(); + return; + } + if (control.type == OpenLayers.Control.TYPE_TOGGLE) { + if (control.active) { + control.deactivate(); + } else { + control.activate(); + } + return; + } + if (this.allowDepress && control.active) { + control.deactivate(); + } else { + var c; + for (var i=0, len=this.controls.length; i} Controls to add in the panel. + */ + addControls: function(controls) { + if (!(OpenLayers.Util.isArray(controls))) { + controls = [controls]; + } + this.controls = this.controls.concat(controls); + + for (var i=0, len=controls.length; i} The control to create the HTML + * markup for. + * + * Returns: + * {DOMElement} The markup. + */ + createControlMarkup: function(control) { + return document.createElement("div"); + }, + + /** + * Method: addControlsToMap + * Only for internal use in draw() and addControls() methods. + * + * Parameters: + * controls - {Array()} Controls to add into map. + */ + addControlsToMap: function (controls) { + var control; + for (var i=0, len=controls.length; i=0; --i) { + if (controls[i].panel_div === button) { + this.activateControl(controls[i]); + break; + } + } + }, + + /** + * APIMethod: getControlsBy + * Get a list of controls with properties matching the given criteria. + * + * Parameters: + * property - {String} A control property to be matched. + * match - {String | Object} A string to match. Can also be a regular + * expression literal or object. In addition, it can be any object + * with a method named test. For reqular expressions or other, if + * match.test(control[property]) evaluates to true, the control will be + * included in the array returned. If no controls are found, an empty + * array is returned. + * + * Returns: + * {Array()} A list of controls matching the given criteria. + * An empty array is returned if no matches are found. + */ + getControlsBy: function(property, match) { + var test = (typeof match.test == "function"); + var found = OpenLayers.Array.filter(this.controls, function(item) { + return item[property] == match || (test && match.test(item[property])); + }); + return found; + }, + + /** + * APIMethod: getControlsByName + * Get a list of contorls with names matching the given name. + * + * Parameters: + * match - {String | Object} A control name. The name can also be a regular + * expression literal or object. In addition, it can be any object + * with a method named test. For reqular expressions or other, if + * name.test(control.name) evaluates to true, the control will be included + * in the list of controls returned. If no controls are found, an empty + * array is returned. + * + * Returns: + * {Array()} A list of controls matching the given name. + * An empty array is returned if no matches are found. + */ + getControlsByName: function(match) { + return this.getControlsBy("name", match); + }, + + /** + * APIMethod: getControlsByClass + * Get a list of controls of a given type (CLASS_NAME). + * + * Parameters: + * match - {String | Object} A control class name. The type can also be a + * regular expression literal or object. In addition, it can be any + * object with a method named test. For reqular expressions or other, + * if type.test(control.CLASS_NAME) evaluates to true, the control will + * be included in the list of controls returned. If no controls are + * found, an empty array is returned. + * + * Returns: + * {Array()} A list of controls matching the given type. + * An empty array is returned if no matches are found. + */ + getControlsByClass: function(match) { + return this.getControlsBy("CLASS_NAME", match); + }, + + CLASS_NAME: "OpenLayers.Control.Panel" +}); + +/* ====================================================================== + OpenLayers/Strategy.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/BaseTypes/Class.js + */ + +/** + * Class: OpenLayers.Strategy + * Abstract vector layer strategy class. Not to be instantiated directly. Use + * one of the strategy subclasses instead. + */ +OpenLayers.Strategy = OpenLayers.Class({ + + /** + * Property: layer + * {} The layer this strategy belongs to. + */ + layer: null, + + /** + * Property: options + * {Object} Any options sent to the constructor. + */ + options: null, + + /** + * Property: active + * {Boolean} The control is active. + */ + active: null, + + /** + * Property: autoActivate + * {Boolean} The creator of the strategy can set autoActivate to false + * to fully control when the protocol is activated and deactivated. + * Defaults to true. + */ + autoActivate: true, + + /** + * Property: autoDestroy + * {Boolean} The creator of the strategy can set autoDestroy to false + * to fully control when the strategy is destroyed. Defaults to + * true. + */ + autoDestroy: true, + + /** + * Constructor: OpenLayers.Strategy + * Abstract class for vector strategies. Create instances of a subclass. + * + * Parameters: + * options - {Object} Optional object whose properties will be set on the + * instance. + */ + initialize: function(options) { + OpenLayers.Util.extend(this, options); + this.options = options; + // set the active property here, so that user cannot override it + this.active = false; + }, + + /** + * APIMethod: destroy + * Clean up the strategy. + */ + destroy: function() { + this.deactivate(); + this.layer = null; + this.options = null; + }, + + /** + * Method: setLayer + * Called to set the property. + * + * Parameters: + * layer - {} + */ + setLayer: function(layer) { + this.layer = layer; + }, + + /** + * Method: activate + * Activate the strategy. Register any listeners, do appropriate setup. + * + * Returns: + * {Boolean} True if the strategy was successfully activated or false if + * the strategy was already active. + */ + activate: function() { + if (!this.active) { + this.active = true; + return true; + } + return false; + }, + + /** + * Method: deactivate + * Deactivate the strategy. Unregister any listeners, do appropriate + * tear-down. + * + * Returns: + * {Boolean} True if the strategy was successfully deactivated or false if + * the strategy was already inactive. + */ + deactivate: function() { + if (this.active) { + this.active = false; + return true; + } + return false; + }, + + CLASS_NAME: "OpenLayers.Strategy" +}); +/* ====================================================================== + OpenLayers/Strategy/Fixed.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Strategy.js + */ + +/** + * Class: OpenLayers.Strategy.Fixed + * A simple strategy that requests features once and never requests new data. + * + * Inherits from: + * - + */ +OpenLayers.Strategy.Fixed = OpenLayers.Class(OpenLayers.Strategy, { + + /** + * APIProperty: preload + * {Boolean} Load data before layer made visible. Enabling this may result + * in considerable overhead if your application loads many data layers + * that are not visible by default. Default is false. + */ + preload: false, + + /** + * Constructor: OpenLayers.Strategy.Fixed + * Create a new Fixed strategy. + * + * Parameters: + * options - {Object} Optional object whose properties will be set on the + * instance. + */ + + /** + * Method: activate + * Activate the strategy: load data or add listener to load when visible + * + * Returns: + * {Boolean} True if the strategy was successfully activated or false if + * the strategy was already active. + */ + activate: function() { + var activated = OpenLayers.Strategy.prototype.activate.apply(this, arguments); + if(activated) { + this.layer.events.on({ + "refresh": this.load, + scope: this + }); + if(this.layer.visibility == true || this.preload) { + this.load(); + } else { + this.layer.events.on({ + "visibilitychanged": this.load, + scope: this + }); + } + } + return activated; + }, + + /** + * Method: deactivate + * Deactivate the strategy. Undo what is done in . + * + * Returns: + * {Boolean} The strategy was successfully deactivated. + */ + deactivate: function() { + var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this); + if(deactivated) { + this.layer.events.un({ + "refresh": this.load, + "visibilitychanged": this.load, + scope: this + }); + } + return deactivated; + }, + + /** + * Method: load + * Tells protocol to load data and unhooks the visibilitychanged event + * + * Parameters: + * options - {Object} options to pass to protocol read. + */ + load: function(options) { + var layer = this.layer; + layer.events.triggerEvent("loadstart", {filter: layer.filter}); + layer.protocol.read(OpenLayers.Util.applyDefaults({ + callback: this.merge, + filter: layer.filter, + scope: this + }, options)); + layer.events.un({ + "visibilitychanged": this.load, + scope: this + }); + }, + + /** + * Method: merge + * Add all features to the layer. + * If the layer projection differs from the map projection, features + * will be transformed from the layer projection to the map projection. + * + * Parameters: + * resp - {} The response object passed + * by the protocol. + */ + merge: function(resp) { + var layer = this.layer; + layer.destroyFeatures(); + var features = resp.features; + if (features && features.length > 0) { + var remote = layer.projection; + var local = layer.map.getProjectionObject(); + if(!local.equals(remote)) { + var geom; + for(var i=0, len=features.length; i + */ +OpenLayers.Control.Zoom = OpenLayers.Class(OpenLayers.Control, { + + /** + * APIProperty: zoomInText + * {String} + * Text for zoom-in link. Default is "+". + */ + zoomInText: "+", + + /** + * APIProperty: zoomInId + * {String} + * Instead of having the control create a zoom in link, you can provide + * the identifier for an anchor element already added to the document. + * By default, an element with id "olZoomInLink" will be searched for + * and used if it exists. + */ + zoomInId: "olZoomInLink", + + /** + * APIProperty: zoomOutText + * {String} + * Text for zoom-out link. Default is "\u2212". + */ + zoomOutText: "\u2212", + + /** + * APIProperty: zoomOutId + * {String} + * Instead of having the control create a zoom out link, you can provide + * the identifier for an anchor element already added to the document. + * By default, an element with id "olZoomOutLink" will be searched for + * and used if it exists. + */ + zoomOutId: "olZoomOutLink", + + /** + * Method: draw + * + * Returns: + * {DOMElement} A reference to the DOMElement containing the zoom links. + */ + draw: function() { + var div = OpenLayers.Control.prototype.draw.apply(this), + links = this.getOrCreateLinks(div), + zoomIn = links.zoomIn, + zoomOut = links.zoomOut, + eventsInstance = this.map.events; + + if (zoomOut.parentNode !== div) { + eventsInstance = this.events; + eventsInstance.attachToElement(zoomOut.parentNode); + } + eventsInstance.register("buttonclick", this, this.onZoomClick); + + this.zoomInLink = zoomIn; + this.zoomOutLink = zoomOut; + return div; + }, + + /** + * Method: getOrCreateLinks + * + * Parameters: + * el - {DOMElement} + * + * Return: + * {Object} Object with zoomIn and zoomOut properties referencing links. + */ + getOrCreateLinks: function(el) { + var zoomIn = document.getElementById(this.zoomInId), + zoomOut = document.getElementById(this.zoomOutId); + if (!zoomIn) { + zoomIn = document.createElement("a"); + zoomIn.href = "#zoomIn"; + zoomIn.appendChild(document.createTextNode(this.zoomInText)); + zoomIn.className = "olControlZoomIn"; + el.appendChild(zoomIn); + } + OpenLayers.Element.addClass(zoomIn, "olButton"); + if (!zoomOut) { + zoomOut = document.createElement("a"); + zoomOut.href = "#zoomOut"; + zoomOut.appendChild(document.createTextNode(this.zoomOutText)); + zoomOut.className = "olControlZoomOut"; + el.appendChild(zoomOut); + } + OpenLayers.Element.addClass(zoomOut, "olButton"); + return { + zoomIn: zoomIn, zoomOut: zoomOut + }; + }, + + /** + * Method: onZoomClick + * Called when zoomin/out link is clicked. + */ + onZoomClick: function(evt) { + var button = evt.buttonElement; + if (button === this.zoomInLink) { + this.map.zoomIn(); + } else if (button === this.zoomOutLink) { + this.map.zoomOut(); + } + }, + + /** + * Method: destroy + * Clean up. + */ + destroy: function() { + if (this.map) { + this.map.events.unregister("buttonclick", this, this.onZoomClick); + } + delete this.zoomInLink; + delete this.zoomOutLink; + OpenLayers.Control.prototype.destroy.apply(this); + }, + + CLASS_NAME: "OpenLayers.Control.Zoom" +}); +/* ====================================================================== + OpenLayers/Protocol.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/BaseTypes/Class.js + */ + +/** + * Class: OpenLayers.Protocol + * Abstract vector layer protocol class. Not to be instantiated directly. Use + * one of the protocol subclasses instead. + */ +OpenLayers.Protocol = OpenLayers.Class({ + + /** + * Property: format + * {} The format used by this protocol. + */ + format: null, + + /** + * Property: options + * {Object} Any options sent to the constructor. + */ + options: null, + + /** + * Property: autoDestroy + * {Boolean} The creator of the protocol can set autoDestroy to false + * to fully control when the protocol is destroyed. Defaults to + * true. + */ + autoDestroy: true, + + /** + * Property: defaultFilter + * {} Optional default filter to read requests + */ + defaultFilter: null, + + /** + * Constructor: OpenLayers.Protocol + * Abstract class for vector protocols. Create instances of a subclass. + * + * Parameters: + * options - {Object} Optional object whose properties will be set on the + * instance. + */ + initialize: function(options) { + options = options || {}; + OpenLayers.Util.extend(this, options); + this.options = options; + }, + + /** + * Method: mergeWithDefaultFilter + * Merge filter passed to the read method with the default one + * + * Parameters: + * filter - {} + */ + mergeWithDefaultFilter: function(filter) { + var merged; + if (filter && this.defaultFilter) { + merged = new OpenLayers.Filter.Logical({ + type: OpenLayers.Filter.Logical.AND, + filters: [this.defaultFilter, filter] + }); + } else { + merged = filter || this.defaultFilter || undefined; + } + return merged; + }, + + /** + * APIMethod: destroy + * Clean up the protocol. + */ + destroy: function() { + this.options = null; + this.format = null; + }, + + /** + * APIMethod: read + * Construct a request for reading new features. + * + * Parameters: + * options - {Object} Optional object for configuring the request. + * + * Returns: + * {} An + * object, the same object will be passed to the callback function passed + * if one exists in the options object. + */ + read: function(options) { + options = options || {}; + options.filter = this.mergeWithDefaultFilter(options.filter); + }, + + + /** + * APIMethod: create + * Construct a request for writing newly created features. + * + * Parameters: + * features - {Array({})} or + * {} + * options - {Object} Optional object for configuring the request. + * + * Returns: + * {} An + * object, the same object will be passed to the callback function passed + * if one exists in the options object. + */ + create: function() { + }, + + /** + * APIMethod: update + * Construct a request updating modified features. + * + * Parameters: + * features - {Array({})} or + * {} + * options - {Object} Optional object for configuring the request. + * + * Returns: + * {} An + * object, the same object will be passed to the callback function passed + * if one exists in the options object. + */ + update: function() { + }, + + /** + * APIMethod: delete + * Construct a request deleting a removed feature. + * + * Parameters: + * feature - {} + * options - {Object} Optional object for configuring the request. + * + * Returns: + * {} An + * object, the same object will be passed to the callback function passed + * if one exists in the options object. + */ + "delete": function() { + }, + + /** + * APIMethod: commit + * Go over the features and for each take action + * based on the feature state. Possible actions are create, + * update and delete. + * + * Parameters: + * features - {Array({})} + * options - {Object} Object whose possible keys are "create", "update", + * "delete", "callback" and "scope", the values referenced by the + * first three are objects as passed to the "create", "update", and + * "delete" methods, the value referenced by the "callback" key is + * a function which is called when the commit operation is complete + * using the scope referenced by the "scope" key. + * + * Returns: + * {Array({})} An array of + * objects. + */ + commit: function() { + }, + + /** + * Method: abort + * Abort an ongoing request. + * + * Parameters: + * response - {} + */ + abort: function(response) { + }, + + /** + * Method: createCallback + * Returns a function that applies the given public method with resp and + * options arguments. + * + * Parameters: + * method - {Function} The method to be applied by the callback. + * response - {} The protocol response object. + * options - {Object} Options sent to the protocol method + */ + createCallback: function(method, response, options) { + return OpenLayers.Function.bind(function() { + method.apply(this, [response, options]); + }, this); + }, + + CLASS_NAME: "OpenLayers.Protocol" +}); + +/** + * Class: OpenLayers.Protocol.Response + * Protocols return Response objects to their users. + */ +OpenLayers.Protocol.Response = OpenLayers.Class({ + /** + * Property: code + * {Number} - OpenLayers.Protocol.Response.SUCCESS or + * OpenLayers.Protocol.Response.FAILURE + */ + code: null, + + /** + * Property: requestType + * {String} The type of request this response corresponds to. Either + * "create", "read", "update" or "delete". + */ + requestType: null, + + /** + * Property: last + * {Boolean} - true if this is the last response expected in a commit, + * false otherwise, defaults to true. + */ + last: true, + + /** + * Property: features + * {Array({})} or {} + * The features returned in the response by the server. Depending on the + * protocol's read payload, either features or data will be populated. + */ + features: null, + + /** + * Property: data + * {Object} + * The data returned in the response by the server. Depending on the + * protocol's read payload, either features or data will be populated. + */ + data: null, + + /** + * Property: reqFeatures + * {Array({})} or {} + * The features provided by the user and placed in the request by the + * protocol. + */ + reqFeatures: null, + + /** + * Property: priv + */ + priv: null, + + /** + * Property: error + * {Object} The error object in case a service exception was encountered. + */ + error: null, + + /** + * Constructor: OpenLayers.Protocol.Response + * + * Parameters: + * options - {Object} Optional object whose properties will be set on the + * instance. + */ + initialize: function(options) { + OpenLayers.Util.extend(this, options); + }, + + /** + * Method: success + * + * Returns: + * {Boolean} - true on success, false otherwise + */ + success: function() { + return this.code > 0; + }, + + CLASS_NAME: "OpenLayers.Protocol.Response" +}); + +OpenLayers.Protocol.Response.SUCCESS = 1; +OpenLayers.Protocol.Response.FAILURE = 0; +/* ====================================================================== + OpenLayers/Protocol/WFS.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Protocol.js + */ + +/** + * Class: OpenLayers.Protocol.WFS + * Used to create a versioned WFS protocol. Default version is 1.0.0. + * + * Returns: + * {} A WFS protocol of the given version. + * + * Example: + * (code) + * var protocol = new OpenLayers.Protocol.WFS({ + * version: "1.1.0", + * url: "http://demo.opengeo.org/geoserver/wfs", + * featureType: "tasmania_roads", + * featureNS: "http://www.openplans.org/topp", + * geometryName: "the_geom" + * }); + * (end) + * + * See the protocols for specific WFS versions for more detail. + */ +OpenLayers.Protocol.WFS = function(options) { + options = OpenLayers.Util.applyDefaults( + options, OpenLayers.Protocol.WFS.DEFAULTS + ); + var cls = OpenLayers.Protocol.WFS["v"+options.version.replace(/\./g, "_")]; + if(!cls) { + throw "Unsupported WFS version: " + options.version; + } + return new cls(options); +}; + +/** + * Function: fromWMSLayer + * Convenience function to create a WFS protocol from a WMS layer. This makes + * the assumption that a WFS requests can be issued at the same URL as + * WMS requests and that a WFS featureType exists with the same name as the + * WMS layer. + * + * This function is designed to auto-configure , , + * and for WFS 1.1.0. Note that + * srsName matching with the WMS layer will not work with WFS 1.0.0. + * + * Parameters: + * layer - {} WMS layer that has a matching WFS + * FeatureType at the same server url with the same typename. + * options - {Object} Default properties to be set on the protocol. + * + * Returns: + * {} + */ +OpenLayers.Protocol.WFS.fromWMSLayer = function(layer, options) { + var typeName, featurePrefix; + var param = layer.params["LAYERS"]; + var parts = (OpenLayers.Util.isArray(param) ? param[0] : param).split(":"); + if(parts.length > 1) { + featurePrefix = parts[0]; + } + typeName = parts.pop(); + var protocolOptions = { + url: layer.url, + featureType: typeName, + featurePrefix: featurePrefix, + srsName: layer.projection && layer.projection.getCode() || + layer.map && layer.map.getProjectionObject().getCode(), + version: "1.1.0" + }; + return new OpenLayers.Protocol.WFS(OpenLayers.Util.applyDefaults( + options, protocolOptions + )); +}; + +/** + * Constant: OpenLayers.Protocol.WFS.DEFAULTS + */ +OpenLayers.Protocol.WFS.DEFAULTS = { + "version": "1.0.0" +}; +/* ====================================================================== + OpenLayers/Request.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Events.js + * @requires OpenLayers/Request/XMLHttpRequest.js + */ + +/** + * TODO: deprecate me + * Use OpenLayers.Request.proxy instead. + */ +OpenLayers.ProxyHost = ""; + +/** + * Namespace: OpenLayers.Request + * The OpenLayers.Request namespace contains convenience methods for working + * with XMLHttpRequests. These methods work with a cross-browser + * W3C compliant class. + */ +if (!OpenLayers.Request) { + /** + * This allows for OpenLayers/Request/XMLHttpRequest.js to be included + * before or after this script. + */ + OpenLayers.Request = {}; +} +OpenLayers.Util.extend(OpenLayers.Request, { + + /** + * Constant: DEFAULT_CONFIG + * {Object} Default configuration for all requests. + */ + DEFAULT_CONFIG: { + method: "GET", + url: window.location.href, + async: true, + user: undefined, + password: undefined, + params: null, + proxy: OpenLayers.ProxyHost, + headers: {}, + data: null, + callback: function() {}, + success: null, + failure: null, + scope: null + }, + + /** + * Constant: URL_SPLIT_REGEX + */ + URL_SPLIT_REGEX: /([^:]*:)\/\/([^:]*:?[^@]*@)?([^:\/\?]*):?([^\/\?]*)/, + + /** + * APIProperty: events + * {} An events object that handles all + * events on the {} object. + * + * All event listeners will receive an event object with three properties: + * request - {} The request object. + * config - {Object} The config object sent to the specific request method. + * requestUrl - {String} The request url. + * + * Supported event types: + * complete - Triggered when we have a response from the request, if a + * listener returns false, no further response processing will take + * place. + * success - Triggered when the HTTP response has a success code (200-299). + * failure - Triggered when the HTTP response does not have a success code. + */ + events: new OpenLayers.Events(this), + + /** + * Method: makeSameOrigin + * Using the specified proxy, returns a same origin url of the provided url. + * + * Parameters: + * url - {String} An arbitrary url + * proxy {String|Function} The proxy to use to make the provided url a + * same origin url. + * + * Returns + * {String} the same origin url. If no proxy is provided, the returned url + * will be the same as the provided url. + */ + makeSameOrigin: function(url, proxy) { + var sameOrigin = url.indexOf("http") !== 0; + var urlParts = !sameOrigin && url.match(this.URL_SPLIT_REGEX); + if (urlParts) { + var location = window.location; + sameOrigin = + urlParts[1] == location.protocol && + urlParts[3] == location.hostname; + var uPort = urlParts[4], lPort = location.port; + if (uPort != 80 && uPort != "" || lPort != "80" && lPort != "") { + sameOrigin = sameOrigin && uPort == lPort; + } + } + if (!sameOrigin) { + if (proxy) { + if (typeof proxy == "function") { + url = proxy(url); + } else { + url = proxy + encodeURIComponent(url); + } + } + } + return url; + }, + + /** + * APIMethod: issue + * Create a new XMLHttpRequest object, open it, set any headers, bind + * a callback to done state, and send any data. It is recommended that + * you use one , , , , , or . + * This method is only documented to provide detail on the configuration + * options available to all request methods. + * + * Parameters: + * config - {Object} Object containing properties for configuring the + * request. Allowed configuration properties are described below. + * This object is modified and should not be reused. + * + * Allowed config properties: + * method - {String} One of GET, POST, PUT, DELETE, HEAD, or + * OPTIONS. Default is GET. + * url - {String} URL for the request. + * async - {Boolean} Open an asynchronous request. Default is true. + * user - {String} User for relevant authentication scheme. Set + * to null to clear current user. + * password - {String} Password for relevant authentication scheme. + * Set to null to clear current password. + * proxy - {String} Optional proxy. Defaults to + * . + * params - {Object} Any key:value pairs to be appended to the + * url as a query string. Assumes url doesn't already include a query + * string or hash. Typically, this is only appropriate for + * requests where the query string will be appended to the url. + * Parameter values that are arrays will be + * concatenated with a comma (note that this goes against form-encoding) + * as is done with . + * headers - {Object} Object with header:value pairs to be set on + * the request. + * data - {String | Document} Optional data to send with the request. + * Typically, this is only used with and requests. + * Make sure to provide the appropriate "Content-Type" header for your + * data. For and requests, the content type defaults to + * "application-xml". If your data is a different content type, or + * if you are using a different HTTP method, set the "Content-Type" + * header to match your data type. + * callback - {Function} Function to call when request is done. + * To determine if the request failed, check request.status (200 + * indicates success). + * success - {Function} Optional function to call if request status is in + * the 200s. This will be called in addition to callback above and + * would typically only be used as an alternative. + * failure - {Function} Optional function to call if request status is not + * in the 200s. This will be called in addition to callback above and + * would typically only be used as an alternative. + * scope - {Object} If callback is a public method on some object, + * set the scope to that object. + * + * Returns: + * {XMLHttpRequest} Request object. To abort the request before a response + * is received, call abort() on the request object. + */ + issue: function(config) { + // apply default config - proxy host may have changed + var defaultConfig = OpenLayers.Util.extend( + this.DEFAULT_CONFIG, + {proxy: OpenLayers.ProxyHost} + ); + config = config || {}; + config.headers = config.headers || {}; + config = OpenLayers.Util.applyDefaults(config, defaultConfig); + config.headers = OpenLayers.Util.applyDefaults(config.headers, defaultConfig.headers); + // Always set the "X-Requested-With" header to signal that this request + // was issued through the XHR-object. Since header keys are case + // insensitive and we want to allow overriding of the "X-Requested-With" + // header through the user we cannot use applyDefaults, but have to + // check manually whether we were called with a "X-Requested-With" + // header. + var customRequestedWithHeader = false, + headerKey; + for(headerKey in config.headers) { + if (config.headers.hasOwnProperty( headerKey )) { + if (headerKey.toLowerCase() === 'x-requested-with') { + customRequestedWithHeader = true; + } + } + } + if (customRequestedWithHeader === false) { + // we did not have a custom "X-Requested-With" header + config.headers['X-Requested-With'] = 'XMLHttpRequest'; + } + + // create request, open, and set headers + var request = new OpenLayers.Request.XMLHttpRequest(); + var url = OpenLayers.Util.urlAppend(config.url, + OpenLayers.Util.getParameterString(config.params || {})); + url = OpenLayers.Request.makeSameOrigin(url, config.proxy); + request.open( + config.method, url, config.async, config.user, config.password + ); + for(var header in config.headers) { + request.setRequestHeader(header, config.headers[header]); + } + + var events = this.events; + + // we want to execute runCallbacks with "this" as the + // execution scope + var self = this; + + request.onreadystatechange = function() { + if(request.readyState == OpenLayers.Request.XMLHttpRequest.DONE) { + var proceed = events.triggerEvent( + "complete", + {request: request, config: config, requestUrl: url} + ); + if(proceed !== false) { + self.runCallbacks( + {request: request, config: config, requestUrl: url} + ); + } + } + }; + + // send request (optionally with data) and return + // call in a timeout for asynchronous requests so the return is + // available before readyState == 4 for cached docs + if(config.async === false) { + request.send(config.data); + } else { + window.setTimeout(function(){ + if (request.readyState !== 0) { // W3C: 0-UNSENT + request.send(config.data); + } + }, 0); + } + return request; + }, + + /** + * Method: runCallbacks + * Calls the complete, success and failure callbacks. Application + * can listen to the "complete" event, have the listener + * display a confirm window and always return false, and + * execute OpenLayers.Request.runCallbacks if the user + * hits "yes" in the confirm window. + * + * Parameters: + * options - {Object} Hash containing request, config and requestUrl keys + */ + runCallbacks: function(options) { + var request = options.request; + var config = options.config; + + // bind callbacks to readyState 4 (done) + var complete = (config.scope) ? + OpenLayers.Function.bind(config.callback, config.scope) : + config.callback; + + // optional success callback + var success; + if(config.success) { + success = (config.scope) ? + OpenLayers.Function.bind(config.success, config.scope) : + config.success; + } + + // optional failure callback + var failure; + if(config.failure) { + failure = (config.scope) ? + OpenLayers.Function.bind(config.failure, config.scope) : + config.failure; + } + + if (OpenLayers.Util.createUrlObject(config.url).protocol == "file:" && + request.responseText) { + request.status = 200; + } + complete(request); + + if (!request.status || (request.status >= 200 && request.status < 300)) { + this.events.triggerEvent("success", options); + if(success) { + success(request); + } + } + if(request.status && (request.status < 200 || request.status >= 300)) { + this.events.triggerEvent("failure", options); + if(failure) { + failure(request); + } + } + }, + + /** + * APIMethod: GET + * Send an HTTP GET request. Additional configuration properties are + * documented in the method, with the method property set + * to GET. + * + * Parameters: + * config - {Object} Object with properties for configuring the request. + * See the method for documentation of allowed properties. + * This object is modified and should not be reused. + * + * Returns: + * {XMLHttpRequest} Request object. + */ + GET: function(config) { + config = OpenLayers.Util.extend(config, {method: "GET"}); + return OpenLayers.Request.issue(config); + }, + + /** + * APIMethod: POST + * Send a POST request. Additional configuration properties are + * documented in the method, with the method property set + * to POST and "Content-Type" header set to "application/xml". + * + * Parameters: + * config - {Object} Object with properties for configuring the request. + * See the method for documentation of allowed properties. The + * default "Content-Type" header will be set to "application-xml" if + * none is provided. This object is modified and should not be reused. + * + * Returns: + * {XMLHttpRequest} Request object. + */ + POST: function(config) { + config = OpenLayers.Util.extend(config, {method: "POST"}); + // set content type to application/xml if it isn't already set + config.headers = config.headers ? config.headers : {}; + if(!("CONTENT-TYPE" in OpenLayers.Util.upperCaseObject(config.headers))) { + config.headers["Content-Type"] = "application/xml"; + } + return OpenLayers.Request.issue(config); + }, + + /** + * APIMethod: PUT + * Send an HTTP PUT request. Additional configuration properties are + * documented in the method, with the method property set + * to PUT and "Content-Type" header set to "application/xml". + * + * Parameters: + * config - {Object} Object with properties for configuring the request. + * See the method for documentation of allowed properties. The + * default "Content-Type" header will be set to "application-xml" if + * none is provided. This object is modified and should not be reused. + * + * Returns: + * {XMLHttpRequest} Request object. + */ + PUT: function(config) { + config = OpenLayers.Util.extend(config, {method: "PUT"}); + // set content type to application/xml if it isn't already set + config.headers = config.headers ? config.headers : {}; + if(!("CONTENT-TYPE" in OpenLayers.Util.upperCaseObject(config.headers))) { + config.headers["Content-Type"] = "application/xml"; + } + return OpenLayers.Request.issue(config); + }, + + /** + * APIMethod: DELETE + * Send an HTTP DELETE request. Additional configuration properties are + * documented in the method, with the method property set + * to DELETE. + * + * Parameters: + * config - {Object} Object with properties for configuring the request. + * See the method for documentation of allowed properties. + * This object is modified and should not be reused. + * + * Returns: + * {XMLHttpRequest} Request object. + */ + DELETE: function(config) { + config = OpenLayers.Util.extend(config, {method: "DELETE"}); + return OpenLayers.Request.issue(config); + }, + + /** + * APIMethod: HEAD + * Send an HTTP HEAD request. Additional configuration properties are + * documented in the method, with the method property set + * to HEAD. + * + * Parameters: + * config - {Object} Object with properties for configuring the request. + * See the method for documentation of allowed properties. + * This object is modified and should not be reused. + * + * Returns: + * {XMLHttpRequest} Request object. + */ + HEAD: function(config) { + config = OpenLayers.Util.extend(config, {method: "HEAD"}); + return OpenLayers.Request.issue(config); + }, + + /** + * APIMethod: OPTIONS + * Send an HTTP OPTIONS request. Additional configuration properties are + * documented in the method, with the method property set + * to OPTIONS. + * + * Parameters: + * config - {Object} Object with properties for configuring the request. + * See the method for documentation of allowed properties. + * This object is modified and should not be reused. + * + * Returns: + * {XMLHttpRequest} Request object. + */ + OPTIONS: function(config) { + config = OpenLayers.Util.extend(config, {method: "OPTIONS"}); + return OpenLayers.Request.issue(config); + } + +}); +/* ====================================================================== + OpenLayers/Request/XMLHttpRequest.js + ====================================================================== */ + +// XMLHttpRequest.js Copyright (C) 2010 Sergey Ilinsky (http://www.ilinsky.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @requires OpenLayers/Request.js + */ + +(function () { + + // Save reference to earlier defined object implementation (if any) + var oXMLHttpRequest = window.XMLHttpRequest; + + // Define on browser type + var bGecko = !!window.controllers, + bIE = window.document.all && !window.opera, + bIE7 = bIE && window.navigator.userAgent.match(/MSIE 7.0/); + + // Enables "XMLHttpRequest()" call next to "new XMLHttpReques()" + function fXMLHttpRequest() { + this._object = oXMLHttpRequest && !bIE7 ? new oXMLHttpRequest : new window.ActiveXObject("Microsoft.XMLHTTP"); + this._listeners = []; + }; + + // Constructor + function cXMLHttpRequest() { + return new fXMLHttpRequest; + }; + cXMLHttpRequest.prototype = fXMLHttpRequest.prototype; + + // BUGFIX: Firefox with Firebug installed would break pages if not executed + if (bGecko && oXMLHttpRequest.wrapped) + cXMLHttpRequest.wrapped = oXMLHttpRequest.wrapped; + + // Constants + cXMLHttpRequest.UNSENT = 0; + cXMLHttpRequest.OPENED = 1; + cXMLHttpRequest.HEADERS_RECEIVED = 2; + cXMLHttpRequest.LOADING = 3; + cXMLHttpRequest.DONE = 4; + + // Public Properties + cXMLHttpRequest.prototype.readyState = cXMLHttpRequest.UNSENT; + cXMLHttpRequest.prototype.responseText = ''; + cXMLHttpRequest.prototype.responseXML = null; + cXMLHttpRequest.prototype.status = 0; + cXMLHttpRequest.prototype.statusText = ''; + + // Priority proposal + cXMLHttpRequest.prototype.priority = "NORMAL"; + + // Instance-level Events Handlers + cXMLHttpRequest.prototype.onreadystatechange = null; + + // Class-level Events Handlers + cXMLHttpRequest.onreadystatechange = null; + cXMLHttpRequest.onopen = null; + cXMLHttpRequest.onsend = null; + cXMLHttpRequest.onabort = null; + + // Public Methods + cXMLHttpRequest.prototype.open = function(sMethod, sUrl, bAsync, sUser, sPassword) { + // Delete headers, required when object is reused + delete this._headers; + + // When bAsync parameter value is omitted, use true as default + if (arguments.length < 3) + bAsync = true; + + // Save async parameter for fixing Gecko bug with missing readystatechange in synchronous requests + this._async = bAsync; + + // Set the onreadystatechange handler + var oRequest = this, + nState = this.readyState, + fOnUnload; + + // BUGFIX: IE - memory leak on page unload (inter-page leak) + if (bIE && bAsync) { + fOnUnload = function() { + if (nState != cXMLHttpRequest.DONE) { + fCleanTransport(oRequest); + // Safe to abort here since onreadystatechange handler removed + oRequest.abort(); + } + }; + window.attachEvent("onunload", fOnUnload); + } + + // Add method sniffer + if (cXMLHttpRequest.onopen) + cXMLHttpRequest.onopen.apply(this, arguments); + + if (arguments.length > 4) + this._object.open(sMethod, sUrl, bAsync, sUser, sPassword); + else + if (arguments.length > 3) + this._object.open(sMethod, sUrl, bAsync, sUser); + else + this._object.open(sMethod, sUrl, bAsync); + + this.readyState = cXMLHttpRequest.OPENED; + fReadyStateChange(this); + + this._object.onreadystatechange = function() { + if (bGecko && !bAsync) + return; + + // Synchronize state + oRequest.readyState = oRequest._object.readyState; + + // + fSynchronizeValues(oRequest); + + // BUGFIX: Firefox fires unnecessary DONE when aborting + if (oRequest._aborted) { + // Reset readyState to UNSENT + oRequest.readyState = cXMLHttpRequest.UNSENT; + + // Return now + return; + } + + if (oRequest.readyState == cXMLHttpRequest.DONE) { + // Free up queue + delete oRequest._data; +/* if (bAsync) + fQueue_remove(oRequest);*/ + // + fCleanTransport(oRequest); +// Uncomment this block if you need a fix for IE cache +/* + // BUGFIX: IE - cache issue + if (!oRequest._object.getResponseHeader("Date")) { + // Save object to cache + oRequest._cached = oRequest._object; + + // Instantiate a new transport object + cXMLHttpRequest.call(oRequest); + + // Re-send request + if (sUser) { + if (sPassword) + oRequest._object.open(sMethod, sUrl, bAsync, sUser, sPassword); + else + oRequest._object.open(sMethod, sUrl, bAsync, sUser); + } + else + oRequest._object.open(sMethod, sUrl, bAsync); + oRequest._object.setRequestHeader("If-Modified-Since", oRequest._cached.getResponseHeader("Last-Modified") || new window.Date(0)); + // Copy headers set + if (oRequest._headers) + for (var sHeader in oRequest._headers) + if (typeof oRequest._headers[sHeader] == "string") // Some frameworks prototype objects with functions + oRequest._object.setRequestHeader(sHeader, oRequest._headers[sHeader]); + + oRequest._object.onreadystatechange = function() { + // Synchronize state + oRequest.readyState = oRequest._object.readyState; + + if (oRequest._aborted) { + // + oRequest.readyState = cXMLHttpRequest.UNSENT; + + // Return + return; + } + + if (oRequest.readyState == cXMLHttpRequest.DONE) { + // Clean Object + fCleanTransport(oRequest); + + // get cached request + if (oRequest.status == 304) + oRequest._object = oRequest._cached; + + // + delete oRequest._cached; + + // + fSynchronizeValues(oRequest); + + // + fReadyStateChange(oRequest); + + // BUGFIX: IE - memory leak in interrupted + if (bIE && bAsync) + window.detachEvent("onunload", fOnUnload); + } + }; + oRequest._object.send(null); + + // Return now - wait until re-sent request is finished + return; + }; +*/ + // BUGFIX: IE - memory leak in interrupted + if (bIE && bAsync) + window.detachEvent("onunload", fOnUnload); + } + + // BUGFIX: Some browsers (Internet Explorer, Gecko) fire OPEN readystate twice + if (nState != oRequest.readyState) + fReadyStateChange(oRequest); + + nState = oRequest.readyState; + } + }; + function fXMLHttpRequest_send(oRequest) { + oRequest._object.send(oRequest._data); + + // BUGFIX: Gecko - missing readystatechange calls in synchronous requests + if (bGecko && !oRequest._async) { + oRequest.readyState = cXMLHttpRequest.OPENED; + + // Synchronize state + fSynchronizeValues(oRequest); + + // Simulate missing states + while (oRequest.readyState < cXMLHttpRequest.DONE) { + oRequest.readyState++; + fReadyStateChange(oRequest); + // Check if we are aborted + if (oRequest._aborted) + return; + } + } + }; + cXMLHttpRequest.prototype.send = function(vData) { + // Add method sniffer + if (cXMLHttpRequest.onsend) + cXMLHttpRequest.onsend.apply(this, arguments); + + if (!arguments.length) + vData = null; + + // BUGFIX: Safari - fails sending documents created/modified dynamically, so an explicit serialization required + // BUGFIX: IE - rewrites any custom mime-type to "text/xml" in case an XMLNode is sent + // BUGFIX: Gecko - fails sending Element (this is up to the implementation either to standard) + if (vData && vData.nodeType) { + vData = window.XMLSerializer ? new window.XMLSerializer().serializeToString(vData) : vData.xml; + if (!this._headers["Content-Type"]) + this._object.setRequestHeader("Content-Type", "application/xml"); + } + + this._data = vData; +/* + // Add to queue + if (this._async) + fQueue_add(this); + else*/ + fXMLHttpRequest_send(this); + }; + cXMLHttpRequest.prototype.abort = function() { + // Add method sniffer + if (cXMLHttpRequest.onabort) + cXMLHttpRequest.onabort.apply(this, arguments); + + // BUGFIX: Gecko - unnecessary DONE when aborting + if (this.readyState > cXMLHttpRequest.UNSENT) + this._aborted = true; + + this._object.abort(); + + // BUGFIX: IE - memory leak + fCleanTransport(this); + + this.readyState = cXMLHttpRequest.UNSENT; + + delete this._data; +/* if (this._async) + fQueue_remove(this);*/ + }; + cXMLHttpRequest.prototype.getAllResponseHeaders = function() { + return this._object.getAllResponseHeaders(); + }; + cXMLHttpRequest.prototype.getResponseHeader = function(sName) { + return this._object.getResponseHeader(sName); + }; + cXMLHttpRequest.prototype.setRequestHeader = function(sName, sValue) { + // BUGFIX: IE - cache issue + if (!this._headers) + this._headers = {}; + this._headers[sName] = sValue; + + return this._object.setRequestHeader(sName, sValue); + }; + + // EventTarget interface implementation + cXMLHttpRequest.prototype.addEventListener = function(sName, fHandler, bUseCapture) { + for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++) + if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture) + return; + // Add listener + this._listeners.push([sName, fHandler, bUseCapture]); + }; + + cXMLHttpRequest.prototype.removeEventListener = function(sName, fHandler, bUseCapture) { + for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++) + if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture) + break; + // Remove listener + if (oListener) + this._listeners.splice(nIndex, 1); + }; + + cXMLHttpRequest.prototype.dispatchEvent = function(oEvent) { + var oEventPseudo = { + 'type': oEvent.type, + 'target': this, + 'currentTarget':this, + 'eventPhase': 2, + 'bubbles': oEvent.bubbles, + 'cancelable': oEvent.cancelable, + 'timeStamp': oEvent.timeStamp, + 'stopPropagation': function() {}, // There is no flow + 'preventDefault': function() {}, // There is no default action + 'initEvent': function() {} // Original event object should be initialized + }; + + // Execute onreadystatechange + if (oEventPseudo.type == "readystatechange" && this.onreadystatechange) + (this.onreadystatechange.handleEvent || this.onreadystatechange).apply(this, [oEventPseudo]); + + // Execute listeners + for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++) + if (oListener[0] == oEventPseudo.type && !oListener[2]) + (oListener[1].handleEvent || oListener[1]).apply(this, [oEventPseudo]); + }; + + // + cXMLHttpRequest.prototype.toString = function() { + return '[' + "object" + ' ' + "XMLHttpRequest" + ']'; + }; + + cXMLHttpRequest.toString = function() { + return '[' + "XMLHttpRequest" + ']'; + }; + + // Helper function + function fReadyStateChange(oRequest) { + // Sniffing code + if (cXMLHttpRequest.onreadystatechange) + cXMLHttpRequest.onreadystatechange.apply(oRequest); + + // Fake event + oRequest.dispatchEvent({ + 'type': "readystatechange", + 'bubbles': false, + 'cancelable': false, + 'timeStamp': new Date + 0 + }); + }; + + function fGetDocument(oRequest) { + var oDocument = oRequest.responseXML, + sResponse = oRequest.responseText; + // Try parsing responseText + if (bIE && sResponse && oDocument && !oDocument.documentElement && oRequest.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/)) { + oDocument = new window.ActiveXObject("Microsoft.XMLDOM"); + oDocument.async = false; + oDocument.validateOnParse = false; + oDocument.loadXML(sResponse); + } + // Check if there is no error in document + if (oDocument) + if ((bIE && oDocument.parseError != 0) || !oDocument.documentElement || (oDocument.documentElement && oDocument.documentElement.tagName == "parsererror")) + return null; + return oDocument; + }; + + function fSynchronizeValues(oRequest) { + try { oRequest.responseText = oRequest._object.responseText; } catch (e) {} + try { oRequest.responseXML = fGetDocument(oRequest._object); } catch (e) {} + try { oRequest.status = oRequest._object.status; } catch (e) {} + try { oRequest.statusText = oRequest._object.statusText; } catch (e) {} + }; + + function fCleanTransport(oRequest) { + // BUGFIX: IE - memory leak (on-page leak) + oRequest._object.onreadystatechange = new window.Function; + }; +/* + // Queue manager + var oQueuePending = {"CRITICAL":[],"HIGH":[],"NORMAL":[],"LOW":[],"LOWEST":[]}, + aQueueRunning = []; + function fQueue_add(oRequest) { + oQueuePending[oRequest.priority in oQueuePending ? oRequest.priority : "NORMAL"].push(oRequest); + // + setTimeout(fQueue_process); + }; + + function fQueue_remove(oRequest) { + for (var nIndex = 0, bFound = false; nIndex < aQueueRunning.length; nIndex++) + if (bFound) + aQueueRunning[nIndex - 1] = aQueueRunning[nIndex]; + else + if (aQueueRunning[nIndex] == oRequest) + bFound = true; + if (bFound) + aQueueRunning.length--; + // + setTimeout(fQueue_process); + }; + + function fQueue_process() { + if (aQueueRunning.length < 6) { + for (var sPriority in oQueuePending) { + if (oQueuePending[sPriority].length) { + var oRequest = oQueuePending[sPriority][0]; + oQueuePending[sPriority] = oQueuePending[sPriority].slice(1); + // + aQueueRunning.push(oRequest); + // Send request + fXMLHttpRequest_send(oRequest); + break; + } + } + } + }; +*/ + // Internet Explorer 5.0 (missing apply) + if (!window.Function.prototype.apply) { + window.Function.prototype.apply = function(oRequest, oArguments) { + if (!oArguments) + oArguments = []; + oRequest.__func = this; + oRequest.__func(oArguments[0], oArguments[1], oArguments[2], oArguments[3], oArguments[4]); + delete oRequest.__func; + }; + }; + + // Register new object with window + /** + * Class: OpenLayers.Request.XMLHttpRequest + * Standard-compliant (W3C) cross-browser implementation of the + * XMLHttpRequest object. From + * http://code.google.com/p/xmlhttprequest/. + */ + if (!OpenLayers.Request) { + /** + * This allows for OpenLayers/Request.js to be included + * before or after this script. + */ + OpenLayers.Request = {}; + } + OpenLayers.Request.XMLHttpRequest = cXMLHttpRequest; +})(); +/* ====================================================================== + OpenLayers/Format/KML.js + ====================================================================== */ + +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/BaseTypes/Date.js + * @requires OpenLayers/Format/XML.js + * @requires OpenLayers/Feature/Vector.js + * @requires OpenLayers/Geometry/Point.js + * @requires OpenLayers/Geometry/LineString.js + * @requires OpenLayers/Geometry/Polygon.js + * @requires OpenLayers/Geometry/Collection.js + * @requires OpenLayers/Request/XMLHttpRequest.js + * @requires OpenLayers/Projection.js + */ + +/** + * Class: OpenLayers.Format.KML + * Read/Write KML. Create a new instance with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Format.KML = OpenLayers.Class(OpenLayers.Format.XML, { + + /** + * Property: namespaces + * {Object} Mapping of namespace aliases to namespace URIs. + */ + namespaces: { + kml: "http://www.opengis.net/kml/2.2", + gx: "http://www.google.com/kml/ext/2.2" + }, + + /** + * APIProperty: kmlns + * {String} KML Namespace to use. Defaults to 2.0 namespace. + */ + kmlns: "http://earth.google.com/kml/2.0", + + /** + * APIProperty: placemarksDesc + * {String} Name of the placemarks. Default is "No description available". + */ + placemarksDesc: "No description available", + + /** + * APIProperty: foldersName + * {String} Name of the folders. Default is "OpenLayers export". + * If set to null, no name element will be created. + */ + foldersName: "OpenLayers export", + + /** + * APIProperty: foldersDesc + * {String} Description of the folders. Default is "Exported on [date]." + * If set to null, no description element will be created. + */ + foldersDesc: "Exported on " + new Date(), + + /** + * APIProperty: extractAttributes + * {Boolean} Extract attributes from KML. Default is true. + * Extracting styleUrls requires this to be set to true + * Note that currently only Data and SimpleData + * elements are handled. + */ + extractAttributes: true, + + /** + * APIProperty: kvpAttributes + * {Boolean} Only used if extractAttributes is true. + * If set to true, attributes will be simple + * key-value pairs, compatible with other formats, + * Any displayName elements will be ignored. + * If set to false, attributes will be objects, + * retaining any displayName elements, but not + * compatible with other formats. Any CDATA in + * displayName will be read in as a string value. + * Default is false. + */ + kvpAttributes: false, + + /** + * Property: extractStyles + * {Boolean} Extract styles from KML. Default is false. + * Extracting styleUrls also requires extractAttributes to be + * set to true + */ + extractStyles: false, + + /** + * APIProperty: extractTracks + * {Boolean} Extract gx:Track elements from Placemark elements. Default + * is false. If true, features will be generated for all points in + * all gx:Track elements. Features will have a when (Date) attribute + * based on when elements in the track. If tracks include angle + * elements, features will have heading, tilt, and roll attributes. + * If track point coordinates have three values, features will have + * an altitude attribute with the third coordinate value. + */ + extractTracks: false, + + /** + * APIProperty: trackAttributes + * {Array} If is true, points within gx:Track elements will + * be parsed as features with when, heading, tilt, and roll attributes. + * Any additional attribute names can be provided in . + */ + trackAttributes: null, + + /** + * Property: internalns + * {String} KML Namespace to use -- defaults to the namespace of the + * Placemark node being parsed, but falls back to kmlns. + */ + internalns: null, + + /** + * Property: features + * {Array} Array of features + * + */ + features: null, + + /** + * Property: styles + * {Object} Storage of style objects + * + */ + styles: null, + + /** + * Property: styleBaseUrl + * {String} + */ + styleBaseUrl: "", + + /** + * Property: fetched + * {Object} Storage of KML URLs that have been fetched before + * in order to prevent reloading them. + */ + fetched: null, + + /** + * APIProperty: maxDepth + * {Integer} Maximum depth for recursive loading external KML URLs + * Defaults to 0: do no external fetching + */ + maxDepth: 0, + + /** + * Constructor: OpenLayers.Format.KML + * Create a new parser for KML. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + initialize: function(options) { + // compile regular expressions once instead of every time they are used + this.regExes = { + trimSpace: (/^\s*|\s*$/g), + removeSpace: (/\s*/g), + splitSpace: (/\s+/), + trimComma: (/\s*,\s*/g), + kmlColor: (/(\w{2})(\w{2})(\w{2})(\w{2})/), + kmlIconPalette: (/root:\/\/icons\/palette-(\d+)(\.\w+)/), + straightBracket: (/\$\[(.*?)\]/g) + }; + // KML coordinates are always in longlat WGS84 + this.externalProjection = new OpenLayers.Projection("EPSG:4326"); + + OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); + }, + + /** + * APIMethod: read + * Read data from a string, and return a list of features. + * + * Parameters: + * data - {String} or {DOMElement} data to read/parse. + * + * Returns: + * {Array()} List of features. + */ + read: function(data) { + this.features = []; + this.styles = {}; + this.fetched = {}; + + // Set default options + var options = { + depth: 0, + styleBaseUrl: this.styleBaseUrl + }; + + return this.parseData(data, options); + }, + + /** + * Method: parseData + * Read data from a string, and return a list of features. + * + * Parameters: + * data - {String} or {DOMElement} data to read/parse. + * options - {Object} Hash of options + * + * Returns: + * {Array()} List of features. + */ + parseData: function(data, options) { + if(typeof data == "string") { + data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); + } + + // Loop throught the following node types in this order and + // process the nodes found + var types = ["Link", "NetworkLink", "Style", "StyleMap", "Placemark"]; + for(var i=0, len=types.length; i and + // Don't do anything if we have reached our maximum depth for recursion + if (options.depth >= this.maxDepth) { + return false; + } + + // increase depth + var newOptions = OpenLayers.Util.extend({}, options); + newOptions.depth++; + + for(var i=0, len=nodes.length; i nodes + * + * Parameters: + * nodes - {Array} of {DOMElement} data to read/parse. + * options - {Object} Hash of options + * + */ + parseStyles: function(nodes, options) { + for(var i=0, len=nodes.length; i node and builds the style hash + * accordingly + * + * Parameters: + * node - {DOMElement}