diff --git a/ajax/libs/openlayers/2.13.1/OpenLayers.debug.js b/ajax/libs/openlayers/2.13.1/OpenLayers.debug.js new file mode 100644 index 000000000..3a5882f6d --- /dev/null +++ b/ajax/libs/openlayers/2.13.1/OpenLayers.debug.js @@ -0,0 +1,85622 @@ +/* + + 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/Class.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 + */ + +/** + * Constructor: OpenLayers.Class + * Base class used to construct all other classes. Includes support for + * multiple inheritance. + * + * This constructor is new in OpenLayers 2.5. At OpenLayers 3.0, the old + * syntax for creating classes and dealing with inheritance + * will be removed. + * + * To create a new OpenLayers-style class, use the following syntax: + * (code) + * var MyClass = OpenLayers.Class(prototype); + * (end) + * + * To create a new OpenLayers-style class with multiple inheritance, use the + * following syntax: + * (code) + * var MyClass = OpenLayers.Class(Class1, Class2, prototype); + * (end) + * + * Note that instanceof reflection will only reveal Class1 as superclass. + * + */ +OpenLayers.Class = function() { + var len = arguments.length; + var P = arguments[0]; + var F = arguments[len-1]; + + var C = typeof F.initialize == "function" ? + F.initialize : + function(){ P.prototype.initialize.apply(this, arguments); }; + + if (len > 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 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} 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/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/CSWGetRecords.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.CSWGetRecords + * Default version is 2.0.2. + * + * Returns: + * {} A CSWGetRecords format of the given version. + */ +OpenLayers.Format.CSWGetRecords = function(options) { + options = OpenLayers.Util.applyDefaults( + options, OpenLayers.Format.CSWGetRecords.DEFAULTS + ); + var cls = OpenLayers.Format.CSWGetRecords["v"+options.version.replace(/\./g, "_")]; + if(!cls) { + throw "Unsupported CSWGetRecords version: " + options.version; + } + return new cls(options); +}; + +/** + * Constant: DEFAULTS + * {Object} Default properties for the CSWGetRecords format. + */ +OpenLayers.Format.CSWGetRecords.DEFAULTS = { + "version": "2.0.2" +}; +/* ====================================================================== + 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/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/Util/vendorPrefix.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 + */ + +OpenLayers.Util = OpenLayers.Util || {}; +/** + * Namespace: OpenLayers.Util.vendorPrefix + * A collection of utility functions to detect vendor prefixed features + */ +OpenLayers.Util.vendorPrefix = (function() { + "use strict"; + + var VENDOR_PREFIXES = ["", "O", "ms", "Moz", "Webkit"], + divStyle = document.createElement("div").style, + cssCache = {}, + jsCache = {}; + + + /** + * Function: domToCss + * Converts a upper camel case DOM style property name to a CSS property + * i.e. transformOrigin -> 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/Handler.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/Events.js + */ + +/** + * Class: OpenLayers.Handler + * Base class to construct a higher-level handler for event sequences. All + * handlers have activate and deactivate methods. In addition, they have + * methods named like browser events. When a handler is activated, any + * additional methods named like a browser event is registered as a + * listener for the corresponding event. When a handler is deactivated, + * those same methods are unregistered as event listeners. + * + * Handlers also typically have a callbacks object with keys named like + * the abstracted events or event sequences that they are in charge of + * handling. The controls that wrap handlers define the methods that + * correspond to these abstract events - so instead of listening for + * individual browser events, they only listen for the abstract events + * defined by the handler. + * + * Handlers are created by controls, which ultimately have the responsibility + * of making changes to the the state of the application. Handlers + * themselves may make temporary changes, but in general are expected to + * return the application in the same state that they found it. + */ +OpenLayers.Handler = OpenLayers.Class({ + + /** + * Property: id + * {String} + */ + id: null, + + /** + * APIProperty: 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. + */ + 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/Click.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.Click + * A handler for mouse clicks. The intention of this handler is to give + * controls more flexibility with handling clicks. Browsers trigger + * click events twice for a double-click. In addition, the mousedown, + * mousemove, mouseup sequence fires a click event. With this handler, + * controls can decide whether to ignore clicks associated with a double + * click. By setting a , controls can also ignore clicks + * that include a drag. Create a new instance with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, { + /** + * APIProperty: delay + * {Number} Number of milliseconds between clicks before the event is + * considered a double-click. + */ + delay: 300, + + /** + * APIProperty: single + * {Boolean} Handle single clicks. Default is true. If false, clicks + * will not be reported. If true, single-clicks will be reported. + */ + single: true, + + /** + * APIProperty: double + * {Boolean} Handle double-clicks. Default is false. + */ + 'double': false, + + /** + * APIProperty: pixelTolerance + * {Number} Maximum number of pixels between mouseup and mousedown for an + * event to be considered a click. Default is 0. If set to an + * integer value, clicks with a drag greater than the value will be + * ignored. This property can only be set when the handler is + * constructed. + */ + pixelTolerance: 0, + + /** + * APIProperty: dblclickTolerance + * {Number} Maximum distance in pixels between clicks for a sequence of + * events to be considered a double click. Default is 13. If the + * distance between two clicks is greater than this value, a double- + * click will not be fired. + */ + dblclickTolerance: 13, + + /** + * APIProperty: stopSingle + * {Boolean} Stop other listeners from being notified of clicks. Default + * is false. If true, any listeners registered before this one for + * click or rightclick events will not be notified. + */ + stopSingle: false, + + /** + * APIProperty: stopDouble + * {Boolean} Stop other listeners from being notified of double-clicks. + * Default is false. If true, any click listeners registered before + * this one will not be notified of *any* double-click events. + * + * The one caveat with stopDouble is that given a map with two click + * handlers, one with stopDouble true and the other with stopSingle + * true, the stopSingle handler should be activated last to get + * uniform cross-browser performance. Since IE triggers one click + * with a dblclick and FF triggers two, if a stopSingle handler is + * activated first, all it gets in IE is a single click when the + * second handler stops propagation on the dblclick. + */ + stopDouble: false, + + /** + * Property: timerId + * {Number} The id of the timeout waiting to clear the . + */ + timerId: null, + + /** + * Property: down + * {Object} Object that store relevant information about the last + * mousedown or touchstart. Its 'xy' OpenLayers.Pixel property gives + * the average location of the mouse/touch event. Its 'touches' + * property records clientX/clientY of each touches. + */ + down: null, + + /** + * Property: last + * {Object} Object that store relevant information about the last + * mousemove or touchmove. Its 'xy' OpenLayers.Pixel property gives + * the average location of the mouse/touch event. Its 'touches' + * property records clientX/clientY of each touches. + */ + last: null, + + /** + * Property: first + * {Object} When waiting for double clicks, this object will store + * information about the first click in a two click sequence. + */ + first: null, + + /** + * Property: rightclickTimerId + * {Number} The id of the right mouse timeout waiting to clear the + * . + */ + rightclickTimerId: null, + + /** + * Constructor: OpenLayers.Handler.Click + * Create a new click handler. + * + * Parameters: + * control - {} The control that is making use of + * this handler. If a handler is being used without a control, the + * handler's setMap method must be overridden to deal properly with + * the map. + * callbacks - {Object} An object with keys corresponding to callbacks + * that will be called by the handler. The callbacks should + * expect to recieve a single argument, the click event. + * Callbacks for 'click' and 'dblclick' are supported. + * options - {Object} Optional object whose properties will be set on the + * handler. + */ + + /** + * Method: touchstart + * Handle touchstart. + * + * Returns: + * {Boolean} Continue propagating this event. + */ + touchstart: function(evt) { + this.startTouch(); + this.down = this.getEventInfo(evt); + this.last = this.getEventInfo(evt); + return true; + }, + + /** + * Method: touchmove + * Store position of last move, because touchend event can have + * an empty "touches" property. + * + * Returns: + * {Boolean} Continue propagating this event. + */ + touchmove: function(evt) { + this.last = this.getEventInfo(evt); + return true; + }, + + /** + * Method: touchend + * Correctly set event xy property, and add lastTouches to have + * touches property from last touchstart or touchmove + * + * Returns: + * {Boolean} Continue propagating this event. + */ + touchend: function(evt) { + // touchstart may not have been allowed to propagate + if (this.down) { + evt.xy = this.last.xy; + evt.lastTouches = this.last.touches; + this.handleSingle(evt); + this.down = null; + } + return true; + }, + + /** + * Method: mousedown + * Handle mousedown. + * + * Returns: + * {Boolean} Continue propagating this event. + */ + mousedown: function(evt) { + this.down = this.getEventInfo(evt); + this.last = this.getEventInfo(evt); + return true; + }, + + /** + * Method: mouseup + * Handle mouseup. Installed to support collection of right mouse events. + * + * Returns: + * {Boolean} Continue propagating this event. + */ + mouseup: function (evt) { + var propagate = true; + + // Collect right mouse clicks from the mouseup + // IE - ignores the second right click in mousedown so using + // mouseup instead + if (this.checkModifiers(evt) && this.control.handleRightClicks && + OpenLayers.Event.isRightClick(evt)) { + propagate = this.rightclick(evt); + } + + return propagate; + }, + + /** + * Method: rightclick + * Handle rightclick. For a dblrightclick, we get two clicks so we need + * to always register for dblrightclick to properly handle single + * clicks. + * + * Returns: + * {Boolean} Continue propagating this event. + */ + rightclick: function(evt) { + if(this.passesTolerance(evt)) { + if(this.rightclickTimerId != null) { + //Second click received before timeout this must be + // a double click + this.clearTimer(); + this.callback('dblrightclick', [evt]); + return !this.stopDouble; + } else { + //Set the rightclickTimerId, send evt only if double is + // true else trigger single + var clickEvent = this['double'] ? + OpenLayers.Util.extend({}, evt) : + this.callback('rightclick', [evt]); + + var delayedRightCall = OpenLayers.Function.bind( + this.delayedRightCall, + this, + clickEvent + ); + this.rightclickTimerId = window.setTimeout( + delayedRightCall, this.delay + ); + } + } + return !this.stopSingle; + }, + + /** + * Method: delayedRightCall + * Sets to null. And optionally triggers the + * rightclick callback if evt is set. + */ + delayedRightCall: function(evt) { + this.rightclickTimerId = null; + if (evt) { + this.callback('rightclick', [evt]); + } + }, + + /** + * Method: click + * Handle click events from the browser. This is registered as a listener + * for click events and should not be called from other events in this + * handler. + * + * Returns: + * {Boolean} Continue propagating this event. + */ + click: function(evt) { + if (!this.last) { + this.last = this.getEventInfo(evt); + } + this.handleSingle(evt); + return !this.stopSingle; + }, + + /** + * Method: dblclick + * Handle dblclick. For a dblclick, we get two clicks in some browsers + * (FF) and one in others (IE). So we need to always register for + * dblclick to properly handle single clicks. This method is registered + * as a listener for the dblclick browser event. It should *not* be + * called by other methods in this handler. + * + * Returns: + * {Boolean} Continue propagating this event. + */ + dblclick: function(evt) { + this.handleDouble(evt); + return !this.stopDouble; + }, + + /** + * Method: handleDouble + * Handle double-click sequence. + */ + handleDouble: function(evt) { + if (this.passesDblclickTolerance(evt)) { + if (this["double"]) { + this.callback("dblclick", [evt]); + } + // to prevent a dblclick from firing the click callback in IE + this.clearTimer(); + } + }, + + /** + * Method: handleSingle + * Handle single click sequence. + */ + handleSingle: function(evt) { + if (this.passesTolerance(evt)) { + if (this.timerId != null) { + // already received a click + if (this.last.touches && this.last.touches.length === 1) { + // touch device, no dblclick event - this may be a double + if (this["double"]) { + // on Android don't let the browser zoom on the page + OpenLayers.Event.preventDefault(evt); + } + this.handleDouble(evt); + } + // if we're not in a touch environment we clear the click timer + // if we've got a second touch, we'll get two touchend events + if (!this.last.touches || this.last.touches.length !== 2) { + this.clearTimer(); + } + } else { + // remember the first click info so we can compare to the second + this.first = this.getEventInfo(evt); + // set the timer, send evt only if single is true + //use a clone of the event object because it will no longer + //be a valid event object in IE in the timer callback + var clickEvent = this.single ? + OpenLayers.Util.extend({}, evt) : null; + this.queuePotentialClick(clickEvent); + } + } + }, + + /** + * Method: queuePotentialClick + * This method is separated out largely to make testing easier (so we + * don't have to override window.setTimeout) + */ + queuePotentialClick: function(evt) { + this.timerId = window.setTimeout( + OpenLayers.Function.bind(this.delayedCall, this, evt), + this.delay + ); + }, + + /** + * Method: passesTolerance + * Determine whether the event is within the optional pixel tolerance. Note + * that the pixel tolerance check only works if mousedown events get to + * the listeners registered here. If they are stopped by other elements, + * the will have no effect here (this method will always + * return true). + * + * Returns: + * {Boolean} The click is within the pixel tolerance (if specified). + */ + passesTolerance: function(evt) { + var passes = true; + if (this.pixelTolerance != null && this.down && this.down.xy) { + passes = this.pixelTolerance >= this.down.xy.distanceTo(evt.xy); + // for touch environments, we also enforce that all touches + // start and end within the given tolerance to be considered a click + if (passes && this.touch && + this.down.touches.length === this.last.touches.length) { + // the touchend event doesn't come with touches, so we check + // down and last + for (var i=0, ii=this.down.touches.length; i this.pixelTolerance) { + passes = false; + break; + } + } + } + } + return passes; + }, + + /** + * Method: getTouchDistance + * + * Returns: + * {Boolean} The pixel displacement between two touches. + */ + getTouchDistance: function(from, to) { + return Math.sqrt( + Math.pow(from.clientX - to.clientX, 2) + + Math.pow(from.clientY - to.clientY, 2) + ); + }, + + /** + * Method: passesDblclickTolerance + * Determine whether the event is within the optional double-cick pixel + * tolerance. + * + * Returns: + * {Boolean} The click is within the double-click pixel tolerance. + */ + passesDblclickTolerance: function(evt) { + var passes = true; + if (this.down && this.first) { + passes = this.down.xy.distanceTo(this.first.xy) <= this.dblclickTolerance; + } + return passes; + }, + + /** + * Method: clearTimer + * Clear the timer and set to null. + */ + clearTimer: function() { + if (this.timerId != null) { + window.clearTimeout(this.timerId); + this.timerId = null; + } + if (this.rightclickTimerId != null) { + window.clearTimeout(this.rightclickTimerId); + this.rightclickTimerId = null; + } + }, + + /** + * Method: delayedCall + * Sets to null. And optionally triggers the click callback if + * evt is set. + */ + delayedCall: function(evt) { + this.timerId = null; + if (evt) { + this.callback("click", [evt]); + } + }, + + /** + * Method: getEventInfo + * This method allows us to store event information without storing the + * actual event. In touch devices (at least), the same event is + * modified between touchstart, touchmove, and touchend. + * + * Returns: + * {Object} An object with event related info. + */ + getEventInfo: function(evt) { + var touches; + if (evt.touches) { + var len = evt.touches.length; + touches = new Array(len); + var touch; + for (var i=0; i 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/Control/OverviewMap.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/BaseTypes.js + * @requires OpenLayers/Events/buttonclick.js + * @requires OpenLayers/Map.js + * @requires OpenLayers/Handler/Click.js + * @requires OpenLayers/Handler/Drag.js + */ + +/** + * Class: OpenLayers.Control.OverviewMap + * The OverMap control creates a small overview map, useful to display the + * extent of a zoomed map and your main map and provide additional + * navigation options to the User. By default the overview map is drawn in + * the lower right corner of the main map. Create a new overview map with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Control.OverviewMap = OpenLayers.Class(OpenLayers.Control, { + + /** + * Property: element + * {DOMElement} The DOM element that contains the overview map + */ + element: null, + + /** + * APIProperty: ovmap + * {} A reference to the overview map itself. + */ + ovmap: null, + + /** + * APIProperty: size + * {} The overvew map size in pixels. Note that this is + * the size of the map itself - the element that contains the map (default + * class name olControlOverviewMapElement) may have padding or other style + * attributes added via CSS. + */ + size: {w: 180, h: 90}, + + /** + * APIProperty: layers + * {Array()} Ordered list of layers in the overview map. + * If none are sent at construction, the base layer for the main map is used. + */ + layers: null, + + /** + * APIProperty: minRectSize + * {Integer} The minimum width or height (in pixels) of the extent + * rectangle on the overview map. When the extent rectangle reaches + * this size, it will be replaced depending on the value of the + * property. Default is 15 pixels. + */ + minRectSize: 15, + + /** + * APIProperty: minRectDisplayClass + * {String} Replacement style class name for the extent rectangle when + * is reached. This string will be suffixed on to the + * displayClass. Default is "RectReplacement". + * + * Example CSS declaration: + * (code) + * .olControlOverviewMapRectReplacement { + * overflow: hidden; + * cursor: move; + * background-image: url("img/overview_replacement.gif"); + * background-repeat: no-repeat; + * background-position: center; + * } + * (end) + */ + minRectDisplayClass: "RectReplacement", + + /** + * APIProperty: minRatio + * {Float} The ratio of the overview map resolution to the main map + * resolution at which to zoom farther out on the overview map. + */ + minRatio: 8, + + /** + * APIProperty: maxRatio + * {Float} The ratio of the overview map resolution to the main map + * resolution at which to zoom farther in on the overview map. + */ + maxRatio: 32, + + /** + * APIProperty: mapOptions + * {Object} An object containing any non-default properties to be sent to + * the overview map's map constructor. These should include any + * non-default options that the main map was constructed with. + */ + mapOptions: null, + + /** + * APIProperty: autoPan + * {Boolean} Always pan the overview map, so the extent marker remains in + * the center. Default is false. If true, when you drag the extent + * marker, the overview map will update itself so the marker returns + * to the center. + */ + autoPan: false, + + /** + * Property: handlers + * {Object} + */ + handlers: null, + + /** + * Property: resolutionFactor + * {Object} + */ + resolutionFactor: 1, + + /** + * APIProperty: maximized + * {Boolean} Start as maximized (visible). Defaults to false. + */ + maximized: false, + + /** + * APIProperty: maximizeTitle + * {String} This property is used for showing a tooltip over the + * maximize div. Defaults to "" (no title). + */ + maximizeTitle: "", + + /** + * APIProperty: minimizeTitle + * {String} This property is used for showing a tooltip over the + * minimize div. Defaults to "" (no title). + */ + minimizeTitle: "", + + /** + * Constructor: OpenLayers.Control.OverviewMap + * Create a new overview map + * + * Parameters: + * options - {Object} Properties of this object will be set on the overview + * map object. Note, to set options on the map object contained in this + * control, set as one of the options properties. + */ + initialize: function(options) { + this.layers = []; + this.handlers = {}; + OpenLayers.Control.prototype.initialize.apply(this, [options]); + }, + + /** + * APIMethod: destroy + * Deconstruct the control + */ + destroy: function() { + if (!this.mapDiv) { // we've already been destroyed + return; + } + if (this.handlers.click) { + this.handlers.click.destroy(); + } + if (this.handlers.drag) { + this.handlers.drag.destroy(); + } + + this.ovmap && this.ovmap.viewPortDiv.removeChild(this.extentRectangle); + this.extentRectangle = null; + + if (this.rectEvents) { + this.rectEvents.destroy(); + this.rectEvents = null; + } + + if (this.ovmap) { + this.ovmap.destroy(); + this.ovmap = null; + } + + this.element.removeChild(this.mapDiv); + this.mapDiv = null; + + this.div.removeChild(this.element); + this.element = null; + + if (this.maximizeDiv) { + this.div.removeChild(this.maximizeDiv); + this.maximizeDiv = null; + } + + if (this.minimizeDiv) { + this.div.removeChild(this.minimizeDiv); + this.minimizeDiv = null; + } + + this.map.events.un({ + buttonclick: this.onButtonClick, + moveend: this.update, + changebaselayer: this.baseLayerDraw, + scope: this + }); + + OpenLayers.Control.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: draw + * Render the control in the browser. + */ + draw: function() { + OpenLayers.Control.prototype.draw.apply(this, arguments); + if (this.layers.length === 0) { + if (this.map.baseLayer) { + var layer = this.map.baseLayer.clone(); + this.layers = [layer]; + } else { + this.map.events.register("changebaselayer", this, this.baseLayerDraw); + return this.div; + } + } + + // create overview map DOM elements + this.element = document.createElement('div'); + this.element.className = this.displayClass + 'Element'; + this.element.style.display = 'none'; + + this.mapDiv = document.createElement('div'); + this.mapDiv.style.width = this.size.w + 'px'; + this.mapDiv.style.height = this.size.h + 'px'; + this.mapDiv.style.position = 'relative'; + this.mapDiv.style.overflow = 'hidden'; + this.mapDiv.id = OpenLayers.Util.createUniqueID('overviewMap'); + + this.extentRectangle = document.createElement('div'); + this.extentRectangle.style.position = 'absolute'; + this.extentRectangle.style.zIndex = 1000; //HACK + this.extentRectangle.className = this.displayClass+'ExtentRectangle'; + + this.element.appendChild(this.mapDiv); + + this.div.appendChild(this.element); + + // Optionally add min/max buttons if the control will go in the + // map viewport. + if(!this.outsideViewport) { + this.div.className += " " + this.displayClass + 'Container'; + // maximize button div + var img = OpenLayers.Util.getImageLocation('layer-switcher-maximize.png'); + this.maximizeDiv = OpenLayers.Util.createAlphaImageDiv( + this.displayClass + 'MaximizeButton', + null, + null, + img, + 'absolute'); + this.maximizeDiv.style.display = 'none'; + this.maximizeDiv.className = this.displayClass + 'MaximizeButton olButton'; + if (this.maximizeTitle) { + this.maximizeDiv.title = this.maximizeTitle; + } + this.div.appendChild(this.maximizeDiv); + + // minimize button div + var img = OpenLayers.Util.getImageLocation('layer-switcher-minimize.png'); + this.minimizeDiv = OpenLayers.Util.createAlphaImageDiv( + 'OpenLayers_Control_minimizeDiv', + null, + null, + img, + 'absolute'); + this.minimizeDiv.style.display = 'none'; + this.minimizeDiv.className = this.displayClass + 'MinimizeButton olButton'; + if (this.minimizeTitle) { + this.minimizeDiv.title = this.minimizeTitle; + } + this.div.appendChild(this.minimizeDiv); + this.minimizeControl(); + } else { + // show the overview map + this.element.style.display = ''; + } + if(this.map.getExtent()) { + this.update(); + } + + this.map.events.on({ + buttonclick: this.onButtonClick, + moveend: this.update, + scope: this + }); + + if (this.maximized) { + this.maximizeControl(); + } + return this.div; + }, + + /** + * Method: baseLayerDraw + * Draw the base layer - called if unable to complete in the initial draw + */ + baseLayerDraw: function() { + this.draw(); + this.map.events.unregister("changebaselayer", this, this.baseLayerDraw); + }, + + /** + * Method: rectDrag + * Handle extent rectangle drag + * + * Parameters: + * px - {} The pixel location of the drag. + */ + rectDrag: function(px) { + var deltaX = this.handlers.drag.last.x - px.x; + var deltaY = this.handlers.drag.last.y - px.y; + if(deltaX != 0 || deltaY != 0) { + var rectTop = this.rectPxBounds.top; + var rectLeft = this.rectPxBounds.left; + var rectHeight = Math.abs(this.rectPxBounds.getHeight()); + var rectWidth = this.rectPxBounds.getWidth(); + // don't allow dragging off of parent element + var newTop = Math.max(0, (rectTop - deltaY)); + newTop = Math.min(newTop, + this.ovmap.size.h - this.hComp - rectHeight); + var newLeft = Math.max(0, (rectLeft - deltaX)); + newLeft = Math.min(newLeft, + this.ovmap.size.w - this.wComp - rectWidth); + this.setRectPxBounds(new OpenLayers.Bounds(newLeft, + newTop + rectHeight, + newLeft + rectWidth, + newTop)); + } + }, + + /** + * Method: mapDivClick + * Handle browser events + * + * Parameters: + * evt - {} evt + */ + mapDivClick: function(evt) { + var pxCenter = this.rectPxBounds.getCenterPixel(); + var deltaX = evt.xy.x - pxCenter.x; + var deltaY = evt.xy.y - pxCenter.y; + var top = this.rectPxBounds.top; + var left = this.rectPxBounds.left; + var height = Math.abs(this.rectPxBounds.getHeight()); + var width = this.rectPxBounds.getWidth(); + var newTop = Math.max(0, (top + deltaY)); + newTop = Math.min(newTop, this.ovmap.size.h - height); + var newLeft = Math.max(0, (left + deltaX)); + newLeft = Math.min(newLeft, this.ovmap.size.w - width); + this.setRectPxBounds(new OpenLayers.Bounds(newLeft, + newTop + height, + newLeft + width, + newTop)); + this.updateMapToRect(); + }, + + /** + * Method: onButtonClick + * + * Parameters: + * evt - {Event} + */ + onButtonClick: function(evt) { + if (evt.buttonElement === this.minimizeDiv) { + this.minimizeControl(); + } else if (evt.buttonElement === this.maximizeDiv) { + this.maximizeControl(); + } + }, + + /** + * Method: maximizeControl + * Unhide the control. Called when the control is in the map viewport. + * + * Parameters: + * e - {} + */ + maximizeControl: function(e) { + this.element.style.display = ''; + this.showToggle(false); + if (e != null) { + OpenLayers.Event.stop(e); + } + }, + + /** + * Method: minimizeControl + * Hide all the contents of the control, shrink the size, + * add the maximize icon + * + * Parameters: + * e - {} + */ + minimizeControl: function(e) { + this.element.style.display = 'none'; + this.showToggle(true); + if (e != null) { + OpenLayers.Event.stop(e); + } + }, + + /** + * Method: showToggle + * Hide/Show the toggle depending on whether the control is minimized + * + * Parameters: + * minimize - {Boolean} + */ + showToggle: function(minimize) { + if (this.maximizeDiv) { + this.maximizeDiv.style.display = minimize ? '' : 'none'; + } + if (this.minimizeDiv) { + this.minimizeDiv.style.display = minimize ? 'none' : ''; + } + }, + + /** + * Method: update + * Update the overview map after layers move. + */ + update: function() { + if(this.ovmap == null) { + this.createMap(); + } + + if(this.autoPan || !this.isSuitableOverview()) { + this.updateOverview(); + } + + // update extent rectangle + this.updateRectToMap(); + }, + + /** + * Method: isSuitableOverview + * Determines if the overview map is suitable given the extent and + * resolution of the main map. + */ + isSuitableOverview: function() { + var mapExtent = this.map.getExtent(); + var maxExtent = this.map.getMaxExtent(); + var testExtent = new OpenLayers.Bounds( + Math.max(mapExtent.left, maxExtent.left), + Math.max(mapExtent.bottom, maxExtent.bottom), + Math.min(mapExtent.right, maxExtent.right), + Math.min(mapExtent.top, maxExtent.top)); + + if (this.ovmap.getProjection() != this.map.getProjection()) { + testExtent = testExtent.transform( + this.map.getProjectionObject(), + this.ovmap.getProjectionObject() ); + } + + var resRatio = this.ovmap.getResolution() / this.map.getResolution(); + return ((resRatio > this.minRatio) && + (resRatio <= this.maxRatio) && + (this.ovmap.getExtent().containsBounds(testExtent))); + }, + + /** + * Method updateOverview + * Called by if returns true + */ + updateOverview: function() { + var mapRes = this.map.getResolution(); + var targetRes = this.ovmap.getResolution(); + var resRatio = targetRes / mapRes; + if(resRatio > this.maxRatio) { + // zoom in overview map + targetRes = this.minRatio * mapRes; + } else if(resRatio <= this.minRatio) { + // zoom out overview map + targetRes = this.maxRatio * mapRes; + } + var center; + if (this.ovmap.getProjection() != this.map.getProjection()) { + center = this.map.center.clone(); + center.transform(this.map.getProjectionObject(), + this.ovmap.getProjectionObject() ); + } else { + center = this.map.center; + } + this.ovmap.setCenter(center, this.ovmap.getZoomForResolution( + targetRes * this.resolutionFactor)); + this.updateRectToMap(); + }, + + /** + * Method: createMap + * Construct the map that this control contains + */ + createMap: function() { + // create the overview map + var options = OpenLayers.Util.extend( + {controls: [], maxResolution: 'auto', + fallThrough: false}, this.mapOptions); + this.ovmap = new OpenLayers.Map(this.mapDiv, options); + this.ovmap.viewPortDiv.appendChild(this.extentRectangle); + + // prevent ovmap from being destroyed when the page unloads, because + // the OverviewMap control has to do this (and does it). + OpenLayers.Event.stopObserving(window, 'unload', this.ovmap.unloadDestroy); + + this.ovmap.addLayers(this.layers); + this.ovmap.zoomToMaxExtent(); + // check extent rectangle border width + this.wComp = parseInt(OpenLayers.Element.getStyle(this.extentRectangle, + 'border-left-width')) + + parseInt(OpenLayers.Element.getStyle(this.extentRectangle, + 'border-right-width')); + this.wComp = (this.wComp) ? this.wComp : 2; + this.hComp = parseInt(OpenLayers.Element.getStyle(this.extentRectangle, + 'border-top-width')) + + parseInt(OpenLayers.Element.getStyle(this.extentRectangle, + 'border-bottom-width')); + this.hComp = (this.hComp) ? this.hComp : 2; + + this.handlers.drag = new OpenLayers.Handler.Drag( + this, {move: this.rectDrag, done: this.updateMapToRect}, + {map: this.ovmap} + ); + this.handlers.click = new OpenLayers.Handler.Click( + this, { + "click": this.mapDivClick + },{ + "single": true, "double": false, + "stopSingle": true, "stopDouble": true, + "pixelTolerance": 1, + map: this.ovmap + } + ); + this.handlers.click.activate(); + + this.rectEvents = new OpenLayers.Events(this, this.extentRectangle, + null, true); + this.rectEvents.register("mouseover", this, function(e) { + if(!this.handlers.drag.active && !this.map.dragging) { + this.handlers.drag.activate(); + } + }); + this.rectEvents.register("mouseout", this, function(e) { + if(!this.handlers.drag.dragging) { + this.handlers.drag.deactivate(); + } + }); + + if (this.ovmap.getProjection() != this.map.getProjection()) { + var sourceUnits = this.map.getProjectionObject().getUnits() || + this.map.units || this.map.baseLayer.units; + var targetUnits = this.ovmap.getProjectionObject().getUnits() || + this.ovmap.units || this.ovmap.baseLayer.units; + this.resolutionFactor = sourceUnits && targetUnits ? + OpenLayers.INCHES_PER_UNIT[sourceUnits] / + OpenLayers.INCHES_PER_UNIT[targetUnits] : 1; + } + }, + + /** + * Method: updateRectToMap + * Updates the extent rectangle position and size to match the map extent + */ + updateRectToMap: function() { + // If the projections differ we need to reproject + var bounds; + if (this.ovmap.getProjection() != this.map.getProjection()) { + bounds = this.map.getExtent().transform( + this.map.getProjectionObject(), + this.ovmap.getProjectionObject() ); + } else { + bounds = this.map.getExtent(); + } + var pxBounds = this.getRectBoundsFromMapBounds(bounds); + if (pxBounds) { + this.setRectPxBounds(pxBounds); + } + }, + + /** + * Method: updateMapToRect + * Updates the map extent to match the extent rectangle position and size + */ + updateMapToRect: function() { + var lonLatBounds = this.getMapBoundsFromRectBounds(this.rectPxBounds); + if (this.ovmap.getProjection() != this.map.getProjection()) { + lonLatBounds = lonLatBounds.transform( + this.ovmap.getProjectionObject(), + this.map.getProjectionObject() ); + } + this.map.panTo(lonLatBounds.getCenterLonLat()); + }, + + /** + * Method: setRectPxBounds + * Set extent rectangle pixel bounds. + * + * Parameters: + * pxBounds - {} + */ + setRectPxBounds: function(pxBounds) { + var top = Math.max(pxBounds.top, 0); + var left = Math.max(pxBounds.left, 0); + var bottom = Math.min(pxBounds.top + Math.abs(pxBounds.getHeight()), + this.ovmap.size.h - this.hComp); + var right = Math.min(pxBounds.left + pxBounds.getWidth(), + this.ovmap.size.w - this.wComp); + var width = Math.max(right - left, 0); + var height = Math.max(bottom - top, 0); + if(width < this.minRectSize || height < this.minRectSize) { + this.extentRectangle.className = this.displayClass + + this.minRectDisplayClass; + var rLeft = left + (width / 2) - (this.minRectSize / 2); + var rTop = top + (height / 2) - (this.minRectSize / 2); + this.extentRectangle.style.top = Math.round(rTop) + 'px'; + this.extentRectangle.style.left = Math.round(rLeft) + 'px'; + this.extentRectangle.style.height = this.minRectSize + 'px'; + this.extentRectangle.style.width = this.minRectSize + 'px'; + } else { + this.extentRectangle.className = this.displayClass + + 'ExtentRectangle'; + this.extentRectangle.style.top = Math.round(top) + 'px'; + this.extentRectangle.style.left = Math.round(left) + 'px'; + this.extentRectangle.style.height = Math.round(height) + 'px'; + this.extentRectangle.style.width = Math.round(width) + 'px'; + } + this.rectPxBounds = new OpenLayers.Bounds( + Math.round(left), Math.round(bottom), + Math.round(right), Math.round(top) + ); + }, + + /** + * Method: getRectBoundsFromMapBounds + * Get the rect bounds from the map bounds. + * + * Parameters: + * lonLatBounds - {} + * + * Returns: + * {}A bounds which is the passed-in map lon/lat extent + * translated into pixel bounds for the overview map + */ + getRectBoundsFromMapBounds: function(lonLatBounds) { + var leftBottomPx = this.getOverviewPxFromLonLat({ + lon: lonLatBounds.left, + lat: lonLatBounds.bottom + }); + var rightTopPx = this.getOverviewPxFromLonLat({ + lon: lonLatBounds.right, + lat: lonLatBounds.top + }); + var bounds = null; + if (leftBottomPx && rightTopPx) { + bounds = new OpenLayers.Bounds(leftBottomPx.x, leftBottomPx.y, + rightTopPx.x, rightTopPx.y); + } + return bounds; + }, + + /** + * Method: getMapBoundsFromRectBounds + * Get the map bounds from the rect bounds. + * + * Parameters: + * pxBounds - {} + * + * Returns: + * {} Bounds which is the passed-in overview rect bounds + * translated into lon/lat bounds for the overview map + */ + getMapBoundsFromRectBounds: function(pxBounds) { + var leftBottomLonLat = this.getLonLatFromOverviewPx({ + x: pxBounds.left, + y: pxBounds.bottom + }); + var rightTopLonLat = this.getLonLatFromOverviewPx({ + x: pxBounds.right, + y: pxBounds.top + }); + return new OpenLayers.Bounds(leftBottomLonLat.lon, leftBottomLonLat.lat, + rightTopLonLat.lon, rightTopLonLat.lat); + }, + + /** + * Method: getLonLatFromOverviewPx + * Get a map location from a pixel location + * + * Parameters: + * overviewMapPx - {|Object} OpenLayers.Pixel or + * an object with a + * 'x' and 'y' properties. + * + * Returns: + * {Object} Location which is the passed-in overview map + * OpenLayers.Pixel, translated into lon/lat by the overview + * map. An object with a 'lon' and 'lat' properties. + */ + getLonLatFromOverviewPx: function(overviewMapPx) { + var size = this.ovmap.size; + var res = this.ovmap.getResolution(); + var center = this.ovmap.getExtent().getCenterLonLat(); + + var deltaX = overviewMapPx.x - (size.w / 2); + var deltaY = overviewMapPx.y - (size.h / 2); + + return { + lon: center.lon + deltaX * res, + lat: center.lat - deltaY * res + }; + }, + + /** + * Method: getOverviewPxFromLonLat + * Get a pixel location from a map location + * + * Parameters: + * lonlat - {|Object} OpenLayers.LonLat or an + * object with a 'lon' and 'lat' properties. + * + * Returns: + * {Object} Location which is the passed-in OpenLayers.LonLat, + * translated into overview map pixels + */ + getOverviewPxFromLonLat: function(lonlat) { + var res = this.ovmap.getResolution(); + var extent = this.ovmap.getExtent(); + if (extent) { + return { + x: Math.round(1/res * (lonlat.lon - extent.left)), + y: Math.round(1/res * (extent.top - lonlat.lat)) + }; + } + }, + + CLASS_NAME: 'OpenLayers.Control.OverviewMap' +}); +/* ====================================================================== + 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/SphericalMercator.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 + * @requires OpenLayers/Projection.js + */ + +/** + * Class: OpenLayers.Layer.SphericalMercator + * A mixin for layers that wraps up the pieces neccesary to have a coordinate + * conversion for working with commercial APIs which use a spherical + * mercator projection. Using this layer as a base layer, additional + * layers can be used as overlays if they are in the same projection. + * + * A layer is given properties of this object by setting the sphericalMercator + * property to true. + * + * More projection information: + * - http://spatialreference.org/ref/user/google-projection/ + * + * Proj4 Text: + * +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 + * +k=1.0 +units=m +nadgrids=@null +no_defs + * + * WKT: + * 900913=PROJCS["WGS84 / Simple Mercator", GEOGCS["WGS 84", + * DATUM["WGS_1984", SPHEROID["WGS_1984", 6378137.0, 298.257223563]], + * PRIMEM["Greenwich", 0.0], UNIT["degree", 0.017453292519943295], + * AXIS["Longitude", EAST], AXIS["Latitude", NORTH]], + * PROJECTION["Mercator_1SP_Google"], + * PARAMETER["latitude_of_origin", 0.0], PARAMETER["central_meridian", 0.0], + * PARAMETER["scale_factor", 1.0], PARAMETER["false_easting", 0.0], + * PARAMETER["false_northing", 0.0], UNIT["m", 1.0], AXIS["x", EAST], + * AXIS["y", NORTH], AUTHORITY["EPSG","900913"]] + */ +OpenLayers.Layer.SphericalMercator = { + + /** + * Method: getExtent + * Get the map's extent. + * + * Returns: + * {} The map extent. + */ + getExtent: function() { + var extent = null; + if (this.sphericalMercator) { + extent = this.map.calculateBounds(); + } else { + extent = OpenLayers.Layer.FixedZoomLevels.prototype.getExtent.apply(this); + } + return extent; + }, + + /** + * Method: getLonLatFromViewPortPx + * Get a map location from a pixel location + * + * Parameters: + * viewPortPx - {} + * + * Returns: + * {} An OpenLayers.LonLat which is the passed-in view + * port OpenLayers.Pixel, translated into lon/lat by map lib + * If the map lib is not loaded or not centered, returns null + */ + getLonLatFromViewPortPx: function (viewPortPx) { + return OpenLayers.Layer.prototype.getLonLatFromViewPortPx.apply(this, arguments); + }, + + /** + * Method: getViewPortPxFromLonLat + * Get a pixel location from a map location + * + * Parameters: + * lonlat - {} + * + * Returns: + * {} An OpenLayers.Pixel which is the passed-in + * OpenLayers.LonLat, translated into view port pixels by map lib + * If map lib is not loaded or not centered, returns null + */ + getViewPortPxFromLonLat: function (lonlat) { + return OpenLayers.Layer.prototype.getViewPortPxFromLonLat.apply(this, arguments); + }, + + /** + * Method: initMercatorParameters + * Set up the mercator parameters on the layer: resolutions, + * projection, units. + */ + initMercatorParameters: function() { + // set up properties for Mercator - assume EPSG:900913 + this.RESOLUTIONS = []; + var maxResolution = 156543.03390625; + for(var zoom=0; zoom<=this.MAX_ZOOM_LEVEL; ++zoom) { + this.RESOLUTIONS[zoom] = maxResolution / Math.pow(2, zoom); + } + this.units = "m"; + this.projection = this.projection || "EPSG:900913"; + }, + + /** + * APIMethod: forwardMercator + * Given a lon,lat in EPSG:4326, return a point in Spherical Mercator. + * + * Parameters: + * lon - {float} + * lat - {float} + * + * Returns: + * {} The coordinates transformed to Mercator. + */ + forwardMercator: (function() { + var gg = new OpenLayers.Projection("EPSG:4326"); + var sm = new OpenLayers.Projection("EPSG:900913"); + return function(lon, lat) { + var point = OpenLayers.Projection.transform({x: lon, y: lat}, gg, sm); + return new OpenLayers.LonLat(point.x, point.y); + }; + })(), + + /** + * APIMethod: inverseMercator + * Given a x,y in Spherical Mercator, return a point in EPSG:4326. + * + * Parameters: + * x - {float} A map x in Spherical Mercator. + * y - {float} A map y in Spherical Mercator. + * + * Returns: + * {} The coordinates transformed to EPSG:4326. + */ + inverseMercator: (function() { + var gg = new OpenLayers.Projection("EPSG:4326"); + var sm = new OpenLayers.Projection("EPSG:900913"); + return function(x, y) { + var point = OpenLayers.Projection.transform({x: x, y: y}, sm, gg); + return new OpenLayers.LonLat(point.x, point.y); + }; + })() + +}; +/* ====================================================================== + OpenLayers/Layer/EventPane.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 + * @requires OpenLayers/Util.js + */ + +/** + * Class: OpenLayers.Layer.EventPane + * Base class for 3rd party layers, providing a DOM element which isolates + * the 3rd-party layer from mouse events. + * Only used by Google layers. + * + * Automatically instantiated by the Google constructor, and not usually instantiated directly. + * + * Create a new event pane layer with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Layer.EventPane = OpenLayers.Class(OpenLayers.Layer, { + + /** + * APIProperty: smoothDragPan + * {Boolean} smoothDragPan determines whether non-public/internal API + * methods are used for better performance while dragging EventPane + * layers. When not in sphericalMercator mode, the smoother dragging + * doesn't actually move north/south directly with the number of + * pixels moved, resulting in a slight offset when you drag your mouse + * north south with this option on. If this visual disparity bothers + * you, you should turn this option off, or use spherical mercator. + * Default is on. + */ + smoothDragPan: true, + + /** + * Property: isBaseLayer + * {Boolean} EventPaned layers are always base layers, by necessity. + */ + isBaseLayer: true, + + /** + * APIProperty: isFixed + * {Boolean} EventPaned layers are fixed by default. + */ + isFixed: true, + + /** + * Property: pane + * {DOMElement} A reference to the element that controls the events. + */ + pane: null, + + + /** + * Property: mapObject + * {Object} This is the object which will be used to load the 3rd party library + * in the case of the google layer, this will be of type GMap, + * in the case of the ve layer, this will be of type VEMap + */ + mapObject: null, + + + /** + * Constructor: OpenLayers.Layer.EventPane + * Create a new event pane layer + * + * Parameters: + * name - {String} + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function(name, options) { + OpenLayers.Layer.prototype.initialize.apply(this, arguments); + if (this.pane == null) { + this.pane = OpenLayers.Util.createDiv(this.div.id + "_EventPane"); + } + }, + + /** + * APIMethod: destroy + * Deconstruct this layer. + */ + destroy: function() { + this.mapObject = null; + this.pane = null; + OpenLayers.Layer.prototype.destroy.apply(this, arguments); + }, + + + /** + * 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. + * + * Parameters: + * map - {} + */ + setMap: function(map) { + OpenLayers.Layer.prototype.setMap.apply(this, arguments); + + this.pane.style.zIndex = parseInt(this.div.style.zIndex) + 1; + this.pane.style.display = this.div.style.display; + this.pane.style.width="100%"; + this.pane.style.height="100%"; + if (OpenLayers.BROWSER_NAME == "msie") { + this.pane.style.background = + "url(" + OpenLayers.Util.getImageLocation("blank.gif") + ")"; + } + + if (this.isFixed) { + this.map.viewPortDiv.appendChild(this.pane); + } else { + this.map.layerContainerDiv.appendChild(this.pane); + } + + // once our layer has been added to the map, we can load it + this.loadMapObject(); + + // if map didn't load, display warning + if (this.mapObject == null) { + this.loadWarningMessage(); + } + }, + + /** + * APIMethod: removeMap + * On being removed from the map, we'll like to remove the invisible 'pane' + * div that we added to it on creation. + * + * Parameters: + * map - {} + */ + removeMap: function(map) { + if (this.pane && this.pane.parentNode) { + this.pane.parentNode.removeChild(this.pane); + } + OpenLayers.Layer.prototype.removeMap.apply(this, arguments); + }, + + /** + * Method: loadWarningMessage + * If we can't load the map lib, then display an error message to the + * user and tell them where to go for help. + * + * This function sets up the layout for the warning message. Each 3rd + * party layer must implement its own getWarningHTML() function to + * provide the actual warning message. + */ + loadWarningMessage:function() { + + this.div.style.backgroundColor = "darkblue"; + + var viewSize = this.map.getSize(); + + var msgW = Math.min(viewSize.w, 300); + var msgH = Math.min(viewSize.h, 200); + var size = new OpenLayers.Size(msgW, msgH); + + var centerPx = new OpenLayers.Pixel(viewSize.w/2, viewSize.h/2); + + var topLeft = centerPx.add(-size.w/2, -size.h/2); + + var div = OpenLayers.Util.createDiv(this.name + "_warning", + topLeft, + size, + null, + null, + null, + "auto"); + + div.style.padding = "7px"; + div.style.backgroundColor = "yellow"; + + div.innerHTML = this.getWarningHTML(); + this.div.appendChild(div); + }, + + /** + * Method: getWarningHTML + * To be implemented by subclasses. + * + * Returns: + * {String} String with information on why layer is broken, how to get + * it working. + */ + getWarningHTML:function() { + //should be implemented by subclasses + return ""; + }, + + /** + * Method: display + * Set the display on the pane + * + * Parameters: + * display - {Boolean} + */ + display: function(display) { + OpenLayers.Layer.prototype.display.apply(this, arguments); + this.pane.style.display = this.div.style.display; + }, + + /** + * Method: setZIndex + * Set the z-index order for the pane. + * + * Parameters: + * zIndex - {int} + */ + setZIndex: function (zIndex) { + OpenLayers.Layer.prototype.setZIndex.apply(this, arguments); + this.pane.style.zIndex = parseInt(this.div.style.zIndex) + 1; + }, + + /** + * 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) { + OpenLayers.Layer.prototype.moveByPx.apply(this, arguments); + + if (this.dragPanMapObject) { + this.dragPanMapObject(dx, -dy); + } else { + this.moveTo(this.map.getCachedCenter()); + } + }, + + /** + * Method: moveTo + * Handle calls to move the layer. + * + * Parameters: + * bounds - {} + * zoomChanged - {Boolean} + * dragging - {Boolean} + */ + moveTo:function(bounds, zoomChanged, dragging) { + OpenLayers.Layer.prototype.moveTo.apply(this, arguments); + + if (this.mapObject != null) { + + var newCenter = this.map.getCenter(); + var newZoom = this.map.getZoom(); + + if (newCenter != null) { + + var moOldCenter = this.getMapObjectCenter(); + var oldCenter = this.getOLLonLatFromMapObjectLonLat(moOldCenter); + + var moOldZoom = this.getMapObjectZoom(); + var oldZoom= this.getOLZoomFromMapObjectZoom(moOldZoom); + + if (!(newCenter.equals(oldCenter)) || newZoom != oldZoom) { + + if (!zoomChanged && oldCenter && this.dragPanMapObject && + this.smoothDragPan) { + var oldPx = this.map.getViewPortPxFromLonLat(oldCenter); + var newPx = this.map.getViewPortPxFromLonLat(newCenter); + this.dragPanMapObject(newPx.x-oldPx.x, oldPx.y-newPx.y); + } else { + var center = this.getMapObjectLonLatFromOLLonLat(newCenter); + var zoom = this.getMapObjectZoomFromOLZoom(newZoom); + this.setMapObjectCenter(center, zoom, dragging); + } + } + } + } + }, + + + /********************************************************/ + /* */ + /* Baselayer Functions */ + /* */ + /********************************************************/ + + /** + * Method: getLonLatFromViewPortPx + * Get a map location from a pixel location + * + * Parameters: + * viewPortPx - {} + * + * Returns: + * {} An OpenLayers.LonLat which is the passed-in view + * port OpenLayers.Pixel, translated into lon/lat by map lib + * If the map lib is not loaded or not centered, returns null + */ + getLonLatFromViewPortPx: function (viewPortPx) { + var lonlat = null; + if ( (this.mapObject != null) && + (this.getMapObjectCenter() != null) ) { + var moPixel = this.getMapObjectPixelFromOLPixel(viewPortPx); + var moLonLat = this.getMapObjectLonLatFromMapObjectPixel(moPixel); + lonlat = this.getOLLonLatFromMapObjectLonLat(moLonLat); + } + return lonlat; + }, + + + /** + * Method: getViewPortPxFromLonLat + * Get a pixel location from a map location + * + * Parameters: + * lonlat - {} + * + * Returns: + * {} An OpenLayers.Pixel which is the passed-in + * OpenLayers.LonLat, translated into view port pixels by map lib + * If map lib is not loaded or not centered, returns null + */ + getViewPortPxFromLonLat: function (lonlat) { + var viewPortPx = null; + if ( (this.mapObject != null) && + (this.getMapObjectCenter() != null) ) { + + var moLonLat = this.getMapObjectLonLatFromOLLonLat(lonlat); + var moPixel = this.getMapObjectPixelFromMapObjectLonLat(moLonLat); + + viewPortPx = this.getOLPixelFromMapObjectPixel(moPixel); + } + return viewPortPx; + }, + + /********************************************************/ + /* */ + /* Translation Functions */ + /* */ + /* The following functions translate Map Object and */ + /* OL formats for Pixel, LonLat */ + /* */ + /********************************************************/ + + // + // TRANSLATION: MapObject LatLng <-> OpenLayers.LonLat + // + + /** + * Method: getOLLonLatFromMapObjectLonLat + * Get an OL style map location from a 3rd party style map location + * + * Parameters + * moLonLat - {Object} + * + * Returns: + * {} An OpenLayers.LonLat, translated from the passed in + * MapObject LonLat + * Returns null if null value is passed in + */ + getOLLonLatFromMapObjectLonLat: function(moLonLat) { + var olLonLat = null; + if (moLonLat != null) { + var lon = this.getLongitudeFromMapObjectLonLat(moLonLat); + var lat = this.getLatitudeFromMapObjectLonLat(moLonLat); + olLonLat = new OpenLayers.LonLat(lon, lat); + } + return olLonLat; + }, + + /** + * Method: getMapObjectLonLatFromOLLonLat + * Get a 3rd party map location from an OL map location. + * + * Parameters: + * olLonLat - {} + * + * Returns: + * {Object} A MapObject LonLat, translated from the passed in + * OpenLayers.LonLat + * Returns null if null value is passed in + */ + getMapObjectLonLatFromOLLonLat: function(olLonLat) { + var moLatLng = null; + if (olLonLat != null) { + moLatLng = this.getMapObjectLonLatFromLonLat(olLonLat.lon, + olLonLat.lat); + } + return moLatLng; + }, + + + // + // TRANSLATION: MapObject Pixel <-> OpenLayers.Pixel + // + + /** + * Method: getOLPixelFromMapObjectPixel + * Get an OL pixel location from a 3rd party pixel location. + * + * Parameters: + * moPixel - {Object} + * + * Returns: + * {} An OpenLayers.Pixel, translated from the passed in + * MapObject Pixel + * Returns null if null value is passed in + */ + getOLPixelFromMapObjectPixel: function(moPixel) { + var olPixel = null; + if (moPixel != null) { + var x = this.getXFromMapObjectPixel(moPixel); + var y = this.getYFromMapObjectPixel(moPixel); + olPixel = new OpenLayers.Pixel(x, y); + } + return olPixel; + }, + + /** + * Method: getMapObjectPixelFromOLPixel + * Get a 3rd party pixel location from an OL pixel location + * + * Parameters: + * olPixel - {} + * + * Returns: + * {Object} A MapObject Pixel, translated from the passed in + * OpenLayers.Pixel + * Returns null if null value is passed in + */ + getMapObjectPixelFromOLPixel: function(olPixel) { + var moPixel = null; + if (olPixel != null) { + moPixel = this.getMapObjectPixelFromXY(olPixel.x, olPixel.y); + } + return moPixel; + }, + + CLASS_NAME: "OpenLayers.Layer.EventPane" +}); +/* ====================================================================== + OpenLayers/Layer/FixedZoomLevels.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.FixedZoomLevels + * Some Layers will already have established zoom levels (like google + * or ve). Instead of trying to determine them and populate a resolutions[] + * Array with those values, we will hijack the resolution functionality + * here. + * + * When you subclass FixedZoomLevels: + * + * The initResolutions() call gets nullified, meaning no resolutions[] array + * is set up. Which would be a big problem getResolution() in Layer, since + * it merely takes map.zoom and indexes into resolutions[]... but.... + * + * The getResolution() call is also overridden. Instead of using the + * resolutions[] array, we simply calculate the current resolution based + * on the current extent and the current map size. But how will we be able + * to calculate the current extent without knowing the resolution...? + * + * The getExtent() function is also overridden. Instead of calculating extent + * based on the center point and the current resolution, we instead + * calculate the extent by getting the lonlats at the top-left and + * bottom-right by using the getLonLatFromViewPortPx() translation function, + * taken from the pixel locations (0,0) and the size of the map. But how + * will we be able to do lonlat-px translation without resolution....? + * + * The getZoomForResolution() method is overridden. Instead of indexing into + * the resolutions[] array, we call OpenLayers.Layer.getExent(), passing in + * the desired resolution. With this extent, we then call getZoomForExtent() + * + * + * Whenever you implement a layer using OpenLayers.Layer.FixedZoomLevels, + * it is your responsibility to provide the following three functions: + * + * - getLonLatFromViewPortPx + * - getViewPortPxFromLonLat + * - getZoomForExtent + * + * ...those three functions should generally be provided by any reasonable + * API that you might be working from. + * + */ +OpenLayers.Layer.FixedZoomLevels = OpenLayers.Class({ + + /********************************************************/ + /* */ + /* Baselayer Functions */ + /* */ + /* The following functions must all be implemented */ + /* by all base layers */ + /* */ + /********************************************************/ + + /** + * Constructor: OpenLayers.Layer.FixedZoomLevels + * Create a new fixed zoom levels layer. + */ + initialize: function() { + //this class is only just to add the following functions... + // nothing to actually do here... but it is probably a good + // idea to have layers that use these functions call this + // inititalize() anyways, in case at some point we decide we + // do want to put some functionality or state in here. + }, + + /** + * Method: initResolutions + * Populate the resolutions array + */ + initResolutions: function() { + + var props = ['minZoomLevel', 'maxZoomLevel', 'numZoomLevels']; + + for(var i=0, len=props.length; i lonlat translation functions on tl and br + * corners of viewport + * + * Returns: + * {} A Bounds object which represents the lon/lat + * bounds of the current viewPort. + */ + getExtent: function () { + var size = this.map.getSize(); + var tl = this.getLonLatFromViewPortPx({ + x: 0, y: 0 + }); + var br = this.getLonLatFromViewPortPx({ + x: size.w, y: size.h + }); + + if ((tl != null) && (br != null)) { + return new OpenLayers.Bounds(tl.lon, br.lat, br.lon, tl.lat); + } else { + return null; + } + }, + + /** + * Method: getZoomForResolution + * Get the zoom level for a given resolution + * + * Parameters: + * resolution - {Float} + * + * Returns: + * {Integer} A suitable zoom level for the specified resolution. + * If no baselayer is set, returns null. + */ + getZoomForResolution: function(resolution) { + + if (this.resolutions != null) { + return OpenLayers.Layer.prototype.getZoomForResolution.apply(this, arguments); + } else { + var extent = OpenLayers.Layer.prototype.getExtent.apply(this, []); + return this.getZoomForExtent(extent); + } + }, + + + + + /********************************************************/ + /* */ + /* Translation Functions */ + /* */ + /* The following functions translate GMaps and OL */ + /* formats for Pixel, LonLat, Bounds, and Zoom */ + /* */ + /********************************************************/ + + + // + // TRANSLATION: MapObject Zoom <-> OpenLayers Zoom + // + + /** + * Method: getOLZoomFromMapObjectZoom + * Get the OL zoom index from the map object zoom level + * + * Parameters: + * moZoom - {Integer} + * + * Returns: + * {Integer} An OpenLayers Zoom level, translated from the passed in zoom + * Returns null if null value is passed in + */ + getOLZoomFromMapObjectZoom: function(moZoom) { + var zoom = null; + if (moZoom != null) { + zoom = moZoom - this.minZoomLevel; + if (this.map.baseLayer !== this) { + zoom = this.map.baseLayer.getZoomForResolution( + this.getResolutionForZoom(zoom) + ); + } + } + return zoom; + }, + + /** + * Method: getMapObjectZoomFromOLZoom + * Get the map object zoom level from the OL zoom level + * + * Parameters: + * olZoom - {Integer} + * + * Returns: + * {Integer} A MapObject level, translated from the passed in olZoom + * Returns null if null value is passed in + */ + getMapObjectZoomFromOLZoom: function(olZoom) { + var zoom = null; + if (olZoom != null) { + zoom = olZoom + this.minZoomLevel; + if (this.map.baseLayer !== this) { + zoom = this.getZoomForResolution( + this.map.baseLayer.getResolutionForZoom(zoom) + ); + } + } + return zoom; + }, + + CLASS_NAME: "OpenLayers.Layer.FixedZoomLevels" +}); + +/* ====================================================================== + OpenLayers/Layer/Google.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/SphericalMercator.js + * @requires OpenLayers/Layer/EventPane.js + * @requires OpenLayers/Layer/FixedZoomLevels.js + * @requires OpenLayers/Lang.js + */ + +/** + * Class: OpenLayers.Layer.Google + * + * Provides a wrapper for Google's Maps API + * Normally the Terms of Use for this API do not allow wrapping, but Google + * have provided written consent to OpenLayers for this - see email in + * http://osgeo-org.1560.n6.nabble.com/Google-Maps-API-Terms-of-Use-changes-tp4910013p4911981.html + * + * Inherits from: + * - + * - + * - + */ +OpenLayers.Layer.Google = OpenLayers.Class( + OpenLayers.Layer.EventPane, + OpenLayers.Layer.FixedZoomLevels, { + + /** + * Constant: MIN_ZOOM_LEVEL + * {Integer} 0 + */ + MIN_ZOOM_LEVEL: 0, + + /** + * Constant: MAX_ZOOM_LEVEL + * {Integer} 21 + */ + MAX_ZOOM_LEVEL: 21, + + /** + * Constant: RESOLUTIONS + * {Array(Float)} Hardcode these resolutions so that they are more closely + * tied with the standard wms projection + */ + RESOLUTIONS: [ + 1.40625, + 0.703125, + 0.3515625, + 0.17578125, + 0.087890625, + 0.0439453125, + 0.02197265625, + 0.010986328125, + 0.0054931640625, + 0.00274658203125, + 0.001373291015625, + 0.0006866455078125, + 0.00034332275390625, + 0.000171661376953125, + 0.0000858306884765625, + 0.00004291534423828125, + 0.00002145767211914062, + 0.00001072883605957031, + 0.00000536441802978515, + 0.00000268220901489257, + 0.0000013411045074462891, + 0.00000067055225372314453 + ], + + /** + * APIProperty: type + * {GMapType} + */ + type: null, + + /** + * APIProperty: wrapDateLine + * {Boolean} Allow user to pan forever east/west. Default is true. + * Setting this to false only restricts panning if + * is true. + */ + wrapDateLine: true, + + /** + * APIProperty: sphericalMercator + * {Boolean} Should the map act as a mercator-projected map? This will + * cause all interactions with the map to be in the actual map + * projection, which allows support for vector drawing, overlaying + * other maps, etc. + */ + sphericalMercator: false, + + /** + * Property: version + * {Number} The version of the Google Maps API + */ + version: null, + + /** + * Constructor: OpenLayers.Layer.Google + * + * Parameters: + * name - {String} A name for the layer. + * options - {Object} An optional object whose properties will be set + * on the layer. + */ + initialize: function(name, options) { + options = options || {}; + if(!options.version) { + options.version = typeof GMap2 === "function" ? "2" : "3"; + } + var mixin = OpenLayers.Layer.Google["v" + + options.version.replace(/\./g, "_")]; + if (mixin) { + OpenLayers.Util.applyDefaults(options, mixin); + } else { + throw "Unsupported Google Maps API version: " + options.version; + } + + OpenLayers.Util.applyDefaults(options, mixin.DEFAULTS); + if (options.maxExtent) { + options.maxExtent = options.maxExtent.clone(); + } + + OpenLayers.Layer.EventPane.prototype.initialize.apply(this, + [name, options]); + OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this, + [name, options]); + + if (this.sphericalMercator) { + OpenLayers.Util.extend(this, OpenLayers.Layer.SphericalMercator); + this.initMercatorParameters(); + } + }, + + /** + * Method: clone + * Create a clone of this layer + * + * Returns: + * {} An exact clone of this layer + */ + clone: function() { + /** + * This method isn't intended to be called by a subclass and it + * doesn't call the same method on the superclass. We don't call + * the super's clone because we don't want properties that are set + * on this layer after initialize (i.e. this.mapObject etc.). + */ + return new OpenLayers.Layer.Google( + this.name, this.getOptions() + ); + }, + + /** + * 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: + * visible - {Boolean} Display the layer (if in range) + */ + setVisibility: function(visible) { + // sharing a map container, opacity has to be set per layer + var opacity = this.opacity == null ? 1 : this.opacity; + OpenLayers.Layer.EventPane.prototype.setVisibility.apply(this, arguments); + this.setOpacity(opacity); + }, + + /** + * APIMethod: display + * Hide or show the Layer + * + * Parameters: + * visible - {Boolean} + */ + display: function(visible) { + if (!this._dragging) { + this.setGMapVisibility(visible); + } + OpenLayers.Layer.EventPane.prototype.display.apply(this, arguments); + }, + + /** + * 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) { + this._dragging = dragging; + OpenLayers.Layer.EventPane.prototype.moveTo.apply(this, arguments); + delete this._dragging; + }, + + /** + * APIMethod: setOpacity + * Sets the opacity for the entire layer (all images) + * + * Parameters: + * opacity - {Float} + */ + setOpacity: function(opacity) { + if (opacity !== this.opacity) { + if (this.map != null) { + this.map.events.triggerEvent("changelayer", { + layer: this, + property: "opacity" + }); + } + this.opacity = opacity; + } + // Though this layer's opacity may not change, we're sharing a container + // and need to update the opacity for the entire container. + if (this.getVisibility()) { + var container = this.getMapContainer(); + OpenLayers.Util.modifyDOMElement( + container, null, null, null, null, null, null, opacity + ); + } + }, + + /** + * APIMethod: destroy + * Clean up this layer. + */ + destroy: function() { + /** + * We have to override this method because the event pane destroy + * deletes the mapObject reference before removing this layer from + * the map. + */ + if (this.map) { + this.setGMapVisibility(false); + var cache = OpenLayers.Layer.Google.cache[this.map.id]; + if (cache && cache.count <= 1) { + this.removeGMapElements(); + } + } + OpenLayers.Layer.EventPane.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: removeGMapElements + * Remove all elements added to the dom. This should only be called if + * this is the last of the Google layers for the given map. + */ + removeGMapElements: function() { + var cache = OpenLayers.Layer.Google.cache[this.map.id]; + if (cache) { + // remove shared elements from dom + var container = this.mapObject && this.getMapContainer(); + if (container && container.parentNode) { + container.parentNode.removeChild(container); + } + var termsOfUse = cache.termsOfUse; + if (termsOfUse && termsOfUse.parentNode) { + termsOfUse.parentNode.removeChild(termsOfUse); + } + var poweredBy = cache.poweredBy; + if (poweredBy && poweredBy.parentNode) { + poweredBy.parentNode.removeChild(poweredBy); + } + if (this.mapObject && window.google && google.maps && + google.maps.event && google.maps.event.clearListeners) { + google.maps.event.clearListeners(this.mapObject, 'tilesloaded'); + } + } + }, + + /** + * APIMethod: removeMap + * On being removed from the map, also remove termsOfUse and poweredBy divs + * + * Parameters: + * map - {} + */ + removeMap: function(map) { + // hide layer before removing + if (this.visibility && this.mapObject) { + this.setGMapVisibility(false); + } + // check to see if last Google layer in this map + var cache = OpenLayers.Layer.Google.cache[map.id]; + if (cache) { + if (cache.count <= 1) { + this.removeGMapElements(); + delete OpenLayers.Layer.Google.cache[map.id]; + } else { + // decrement the layer count + --cache.count; + } + } + // remove references to gmap elements + delete this.termsOfUse; + delete this.poweredBy; + delete this.mapObject; + delete this.dragObject; + OpenLayers.Layer.EventPane.prototype.removeMap.apply(this, arguments); + }, + + // + // TRANSLATION: MapObject Bounds <-> OpenLayers.Bounds + // + + /** + * APIMethod: getOLBoundsFromMapObjectBounds + * + * Parameters: + * moBounds - {Object} + * + * Returns: + * {} An , translated from the + * passed-in MapObject Bounds. + * Returns null if null value is passed in. + */ + getOLBoundsFromMapObjectBounds: function(moBounds) { + var olBounds = null; + if (moBounds != null) { + var sw = moBounds.getSouthWest(); + var ne = moBounds.getNorthEast(); + if (this.sphericalMercator) { + sw = this.forwardMercator(sw.lng(), sw.lat()); + ne = this.forwardMercator(ne.lng(), ne.lat()); + } else { + sw = new OpenLayers.LonLat(sw.lng(), sw.lat()); + ne = new OpenLayers.LonLat(ne.lng(), ne.lat()); + } + olBounds = new OpenLayers.Bounds(sw.lon, + sw.lat, + ne.lon, + ne.lat ); + } + return olBounds; + }, + + /** + * APIMethod: getWarningHTML + * + * Returns: + * {String} String with information on why layer is broken, how to get + * it working. + */ + getWarningHTML:function() { + return OpenLayers.i18n("googleWarning"); + }, + + + /************************************ + * * + * MapObject Interface Controls * + * * + ************************************/ + + + // Get&Set Center, Zoom + + /** + * APIMethod: getMapObjectCenter + * + * Returns: + * {Object} The mapObject's current center in Map Object format + */ + getMapObjectCenter: function() { + return this.mapObject.getCenter(); + }, + + /** + * APIMethod: getMapObjectZoom + * + * Returns: + * {Integer} The mapObject's current zoom, in Map Object format + */ + getMapObjectZoom: function() { + return this.mapObject.getZoom(); + }, + + + /************************************ + * * + * MapObject Primitives * + * * + ************************************/ + + + // LonLat + + /** + * APIMethod: getLongitudeFromMapObjectLonLat + * + * Parameters: + * moLonLat - {Object} MapObject LonLat format + * + * Returns: + * {Float} Longitude of the given MapObject LonLat + */ + getLongitudeFromMapObjectLonLat: function(moLonLat) { + return this.sphericalMercator ? + this.forwardMercator(moLonLat.lng(), moLonLat.lat()).lon : + moLonLat.lng(); + }, + + /** + * APIMethod: getLatitudeFromMapObjectLonLat + * + * Parameters: + * moLonLat - {Object} MapObject LonLat format + * + * Returns: + * {Float} Latitude of the given MapObject LonLat + */ + getLatitudeFromMapObjectLonLat: function(moLonLat) { + var lat = this.sphericalMercator ? + this.forwardMercator(moLonLat.lng(), moLonLat.lat()).lat : + moLonLat.lat(); + return lat; + }, + + // Pixel + + /** + * APIMethod: getXFromMapObjectPixel + * + * Parameters: + * moPixel - {Object} MapObject Pixel format + * + * Returns: + * {Integer} X value of the MapObject Pixel + */ + getXFromMapObjectPixel: function(moPixel) { + return moPixel.x; + }, + + /** + * APIMethod: getYFromMapObjectPixel + * + * Parameters: + * moPixel - {Object} MapObject Pixel format + * + * Returns: + * {Integer} Y value of the MapObject Pixel + */ + getYFromMapObjectPixel: function(moPixel) { + return moPixel.y; + }, + + CLASS_NAME: "OpenLayers.Layer.Google" +}); + +/** + * Property: OpenLayers.Layer.Google.cache + * {Object} Cache for elements that should only be created once per map. + */ +OpenLayers.Layer.Google.cache = {}; + + +/** + * Constant: OpenLayers.Layer.Google.v2 + * + * Mixin providing functionality specific to the Google Maps API v2. + * + * This API has been deprecated by Google. + * Developers are encouraged to migrate to v3 of the API; support for this + * is provided by + */ +OpenLayers.Layer.Google.v2 = { + + /** + * Property: termsOfUse + * {DOMElement} Div for Google's copyright and terms of use link + */ + termsOfUse: null, + + /** + * Property: poweredBy + * {DOMElement} Div for Google's powered by logo and link + */ + poweredBy: null, + + /** + * Property: dragObject + * {GDraggableObject} Since 2.93, Google has exposed the ability to get + * the maps GDraggableObject. We can now use this for smooth panning + */ + dragObject: null, + + /** + * Method: loadMapObject + * Load the GMap and register appropriate event listeners. If we can't + * load GMap2, then display a warning message. + */ + loadMapObject:function() { + if (!this.type) { + this.type = G_NORMAL_MAP; + } + var mapObject, termsOfUse, poweredBy; + var cache = OpenLayers.Layer.Google.cache[this.map.id]; + if (cache) { + // there are already Google layers added to this map + mapObject = cache.mapObject; + termsOfUse = cache.termsOfUse; + poweredBy = cache.poweredBy; + // increment the layer count + ++cache.count; + } else { + // this is the first Google layer for this map + + var container = this.map.viewPortDiv; + var div = document.createElement("div"); + div.id = this.map.id + "_GMap2Container"; + div.style.position = "absolute"; + div.style.width = "100%"; + div.style.height = "100%"; + container.appendChild(div); + + // create GMap and shuffle elements + try { + mapObject = new GMap2(div); + + // move the ToS and branding stuff up to the container div + termsOfUse = div.lastChild; + container.appendChild(termsOfUse); + termsOfUse.style.zIndex = "1100"; + termsOfUse.style.right = ""; + termsOfUse.style.bottom = ""; + termsOfUse.className = "olLayerGoogleCopyright"; + + poweredBy = div.lastChild; + container.appendChild(poweredBy); + poweredBy.style.zIndex = "1100"; + poweredBy.style.right = ""; + poweredBy.style.bottom = ""; + poweredBy.className = "olLayerGooglePoweredBy gmnoprint"; + + } catch (e) { + throw(e); + } + // cache elements for use by any other google layers added to + // this same map + OpenLayers.Layer.Google.cache[this.map.id] = { + mapObject: mapObject, + termsOfUse: termsOfUse, + poweredBy: poweredBy, + count: 1 + }; + } + + this.mapObject = mapObject; + this.termsOfUse = termsOfUse; + this.poweredBy = poweredBy; + + // ensure this layer type is one of the mapObject types + if (OpenLayers.Util.indexOf(this.mapObject.getMapTypes(), + this.type) === -1) { + this.mapObject.addMapType(this.type); + } + + //since v 2.93 getDragObject is now available. + if(typeof mapObject.getDragObject == "function") { + this.dragObject = mapObject.getDragObject(); + } else { + this.dragPanMapObject = null; + } + + if(this.isBaseLayer === false) { + this.setGMapVisibility(this.div.style.display !== "none"); + } + + }, + + /** + * APIMethod: onMapResize + */ + onMapResize: function() { + // workaround for resizing of invisible or not yet fully loaded layers + // where GMap2.checkResize() does not work. We need to load the GMap + // for the old div size, then checkResize(), and then call + // layer.moveTo() to trigger GMap.setCenter() (which will finish + // the GMap initialization). + if(this.visibility && this.mapObject.isLoaded()) { + this.mapObject.checkResize(); + } else { + if(!this._resized) { + var layer = this; + var handle = GEvent.addListener(this.mapObject, "load", function() { + GEvent.removeListener(handle); + delete layer._resized; + layer.mapObject.checkResize(); + layer.moveTo(layer.map.getCenter(), layer.map.getZoom()); + }); + } + this._resized = true; + } + }, + + /** + * Method: setGMapVisibility + * Display the GMap container and associated elements. + * + * Parameters: + * visible - {Boolean} Display the GMap elements. + */ + setGMapVisibility: function(visible) { + var cache = OpenLayers.Layer.Google.cache[this.map.id]; + if (cache) { + var container = this.mapObject.getContainer(); + if (visible === true) { + this.mapObject.setMapType(this.type); + container.style.display = ""; + this.termsOfUse.style.left = ""; + this.termsOfUse.style.display = ""; + this.poweredBy.style.display = ""; + cache.displayed = this.id; + } else { + if (cache.displayed === this.id) { + delete cache.displayed; + } + if (!cache.displayed) { + container.style.display = "none"; + this.termsOfUse.style.display = "none"; + // move ToU far to the left in addition to setting display + // to "none", because at the end of the GMap2 load + // sequence, display: none will be unset and ToU would be + // visible after loading a map with a google layer that is + // initially hidden. + this.termsOfUse.style.left = "-9999px"; + this.poweredBy.style.display = "none"; + } + } + } + }, + + /** + * Method: getMapContainer + * + * Returns: + * {DOMElement} the GMap container's div + */ + getMapContainer: function() { + return this.mapObject.getContainer(); + }, + + // + // TRANSLATION: MapObject Bounds <-> OpenLayers.Bounds + // + + /** + * APIMethod: getMapObjectBoundsFromOLBounds + * + * Parameters: + * olBounds - {} + * + * Returns: + * {Object} A MapObject Bounds, translated from olBounds + * Returns null if null value is passed in + */ + getMapObjectBoundsFromOLBounds: function(olBounds) { + var moBounds = null; + if (olBounds != null) { + var sw = this.sphericalMercator ? + this.inverseMercator(olBounds.bottom, olBounds.left) : + new OpenLayers.LonLat(olBounds.bottom, olBounds.left); + var ne = this.sphericalMercator ? + this.inverseMercator(olBounds.top, olBounds.right) : + new OpenLayers.LonLat(olBounds.top, olBounds.right); + moBounds = new GLatLngBounds(new GLatLng(sw.lat, sw.lon), + new GLatLng(ne.lat, ne.lon)); + } + return moBounds; + }, + + + /************************************ + * * + * MapObject Interface Controls * + * * + ************************************/ + + + // Get&Set Center, Zoom + + /** + * APIMethod: setMapObjectCenter + * Set the mapObject to the specified center and zoom + * + * Parameters: + * center - {Object} MapObject LonLat format + * zoom - {int} MapObject zoom format + */ + setMapObjectCenter: function(center, zoom) { + this.mapObject.setCenter(center, zoom); + }, + + /** + * APIMethod: dragPanMapObject + * + * Parameters: + * dX - {Integer} + * dY - {Integer} + */ + dragPanMapObject: function(dX, dY) { + this.dragObject.moveBy(new GSize(-dX, dY)); + }, + + + // LonLat - Pixel Translation + + /** + * APIMethod: getMapObjectLonLatFromMapObjectPixel + * + * Parameters: + * moPixel - {Object} MapObject Pixel format + * + * Returns: + * {Object} MapObject LonLat translated from MapObject Pixel + */ + getMapObjectLonLatFromMapObjectPixel: function(moPixel) { + return this.mapObject.fromContainerPixelToLatLng(moPixel); + }, + + /** + * APIMethod: getMapObjectPixelFromMapObjectLonLat + * + * Parameters: + * moLonLat - {Object} MapObject LonLat format + * + * Returns: + * {Object} MapObject Pixel transtlated from MapObject LonLat + */ + getMapObjectPixelFromMapObjectLonLat: function(moLonLat) { + return this.mapObject.fromLatLngToContainerPixel(moLonLat); + }, + + + // Bounds + + /** + * APIMethod: getMapObjectZoomFromMapObjectBounds + * + * Parameters: + * moBounds - {Object} MapObject Bounds format + * + * Returns: + * {Object} MapObject Zoom for specified MapObject Bounds + */ + getMapObjectZoomFromMapObjectBounds: function(moBounds) { + return this.mapObject.getBoundsZoomLevel(moBounds); + }, + + /************************************ + * * + * MapObject Primitives * + * * + ************************************/ + + + // LonLat + + /** + * APIMethod: getMapObjectLonLatFromLonLat + * + * Parameters: + * lon - {Float} + * lat - {Float} + * + * Returns: + * {Object} MapObject LonLat built from lon and lat params + */ + getMapObjectLonLatFromLonLat: function(lon, lat) { + var gLatLng; + if(this.sphericalMercator) { + var lonlat = this.inverseMercator(lon, lat); + gLatLng = new GLatLng(lonlat.lat, lonlat.lon); + } else { + gLatLng = new GLatLng(lat, lon); + } + return gLatLng; + }, + + // Pixel + + /** + * APIMethod: getMapObjectPixelFromXY + * + * Parameters: + * x - {Integer} + * y - {Integer} + * + * Returns: + * {Object} MapObject Pixel from x and y parameters + */ + getMapObjectPixelFromXY: function(x, y) { + return new GPoint(x, y); + } + +}; +/* ====================================================================== + 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/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/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/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/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/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.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/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/Filter/Function.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.Function + * This class represents a filter function. + * We are using this class for creation of complex + * filters that can contain filter functions as values. + * Nesting function as other functions parameter is supported. + * + * Inherits from: + * - + */ +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/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/Point.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.Point + * Point geometry class. + * + * Inherits from: + * - + */ +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/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 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/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/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 + * - + */ +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/v3.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.v3 + * Parses GML version 3. + * + * Inherits from: + * - + */ +OpenLayers.Format.GML.v3 = OpenLayers.Class(OpenLayers.Format.GML.Base, { + + /** + * Property: schemaLocation + * {String} Schema location for a particular minor version. The writers + * conform with the Simple Features Profile for GML. + */ + schemaLocation: "http://www.opengis.net/gml http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/1.0.0/gmlsf.xsd", + + /** + * Property: curve + * {Boolean} Write gml:Curve instead of gml:LineString elements. This also + * affects the elements in multi-part geometries. Default is false. + * To write gml:Curve elements instead of gml:LineString, set curve + * to true in the options to the contstructor (cannot be changed after + * instantiation). + */ + curve: false, + + /** + * Property: multiCurve + * {Boolean} Write gml:MultiCurve instead of gml:MultiLineString. Since + * the latter is deprecated in GML 3, the default is true. To write + * gml:MultiLineString instead of gml:MultiCurve, set multiCurve to + * false in the options to the constructor (cannot be changed after + * instantiation). + */ + multiCurve: true, + + /** + * Property: surface + * {Boolean} Write gml:Surface instead of gml:Polygon elements. This also + * affects the elements in multi-part geometries. Default is false. + * To write gml:Surface elements instead of gml:Polygon, set surface + * to true in the options to the contstructor (cannot be changed after + * instantiation). + */ + surface: false, + + /** + * Property: multiSurface + * {Boolean} Write gml:multiSurface instead of gml:MultiPolygon. Since + * the latter is deprecated in GML 3, the default is true. To write + * gml:MultiPolygon instead of gml:multiSurface, set multiSurface to + * false in the options to the constructor (cannot be changed after + * instantiation). + */ + multiSurface: true, + + /** + * Constructor: OpenLayers.Format.GML.v3 + * Create a parser for GML v3. + * + * 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({ + "_inherit": function(node, obj, container) { + // SRSReferenceGroup attributes + var dim = parseInt(node.getAttribute("srsDimension"), 10) || + (container && container.srsDimension); + if (dim) { + obj.srsDimension = dim; + } + }, + "featureMembers": function(node, obj) { + this.readChildNodes(node, obj); + }, + "Curve": function(node, container) { + var obj = {points: []}; + this.readers.gml._inherit.apply(this, [node, obj, container]); + this.readChildNodes(node, obj); + if(!container.components) { + container.components = []; + } + container.components.push( + new OpenLayers.Geometry.LineString(obj.points) + ); + }, + "segments": function(node, obj) { + this.readChildNodes(node, obj); + }, + "LineStringSegment": function(node, container) { + var obj = {}; + this.readChildNodes(node, obj); + if(obj.points) { + Array.prototype.push.apply(container.points, obj.points); + } + }, + "pos": function(node, obj) { + var str = this.getChildValue(node).replace( + this.regExes.trimSpace, "" + ); + var coords = str.split(this.regExes.splitSpace); + var point; + if(this.xy) { + point = new OpenLayers.Geometry.Point( + coords[0], coords[1], coords[2] + ); + } else { + point = new OpenLayers.Geometry.Point( + coords[1], coords[0], coords[2] + ); + } + obj.points = [point]; + }, + "posList": function(node, obj) { + var str = this.getChildValue(node).replace( + this.regExes.trimSpace, "" + ); + var coords = str.split(this.regExes.splitSpace); + // The "dimension" attribute is from the GML 3.0.1 spec. + var dim = obj.srsDimension || + parseInt(node.getAttribute("srsDimension") || node.getAttribute("dimension"), 10) || 2; + var j, x, y, z; + var numPoints = coords.length / dim; + var points = new Array(numPoints); + for(var i=0, len=coords.length; i 0) { + container.components = [ + new OpenLayers.Geometry.MultiLineString(obj.components) + ]; + } + }, + "curveMember": function(node, obj) { + this.readChildNodes(node, obj); + }, + "MultiSurface": function(node, container) { + var obj = {components: []}; + this.readers.gml._inherit.apply(this, [node, obj, container]); + this.readChildNodes(node, obj); + if(obj.components.length > 0) { + container.components = [ + new OpenLayers.Geometry.MultiPolygon(obj.components) + ]; + } + }, + "surfaceMember": function(node, obj) { + this.readChildNodes(node, obj); + }, + "surfaceMembers": function(node, obj) { + this.readChildNodes(node, obj); + }, + "pointMembers": function(node, obj) { + this.readChildNodes(node, obj); + }, + "lineStringMembers": function(node, obj) { + this.readChildNodes(node, obj); + }, + "polygonMembers": function(node, obj) { + this.readChildNodes(node, obj); + }, + "geometryMembers": function(node, obj) { + this.readChildNodes(node, obj); + }, + "Envelope": function(node, container) { + var obj = {points: new Array(2)}; + 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) + ); + }, + "lowerCorner": function(node, container) { + var obj = {}; + this.readers.gml.pos.apply(this, [node, obj]); + container.points[0] = obj.points[0]; + }, + "upperCorner": function(node, container) { + var obj = {}; + this.readers.gml.pos.apply(this, [node, obj]); + container.points[1] = obj.points[0]; + } + }, 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)) { + 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": OpenLayers.Util.applyDefaults({ + "featureMembers": function(features) { + var node = this.createElementNSPlus("gml:featureMembers"); + for(var i=0, len=features.length; i mapping. + */ + setGeometryTypes: function() { + this.geometryTypes = { + "OpenLayers.Geometry.Point": "Point", + "OpenLayers.Geometry.MultiPoint": "MultiPoint", + "OpenLayers.Geometry.LineString": (this.curve === true) ? "Curve": "LineString", + "OpenLayers.Geometry.MultiLineString": (this.multiCurve === false) ? "MultiLineString" : "MultiCurve", + "OpenLayers.Geometry.Polygon": (this.surface === true) ? "Surface" : "Polygon", + "OpenLayers.Geometry.MultiPolygon": (this.multiSurface === false) ? "MultiPolygon" : "MultiSurface", + "OpenLayers.Geometry.Collection": "GeometryCollection" + }; + }, + + CLASS_NAME: "OpenLayers.Format.GML.v3" + +}); +/* ====================================================================== + OpenLayers/Format/Filter/v1_1_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/Filter/v1.js + * @requires OpenLayers/Format/GML/v3.js + */ + +/** + * Class: OpenLayers.Format.Filter.v1_1_0 + * Write ogc:Filter version 1.1.0. + * + * Differences from the v1.0.0 parser: + * - uses GML v3 instead of GML v2 + * - reads matchCase attribute on ogc:PropertyIsEqual and + * ogc:PropertyIsNotEqual elements. + * - writes matchCase attribute from comparison filters of type EQUAL_TO, + * NOT_EQUAL_TO and LIKE. + * + * Inherits from: + * - + * - + */ +OpenLayers.Format.Filter.v1_1_0 = OpenLayers.Class( + OpenLayers.Format.GML.v3, OpenLayers.Format.Filter.v1, { + + /** + * Constant: VERSION + * {String} 1.1.0 + */ + VERSION: "1.1.0", + + /** + * Property: schemaLocation + * {String} http://www.opengis.net/ogc/filter/1.1.0/filter.xsd + */ + schemaLocation: "http://www.opengis.net/ogc/filter/1.1.0/filter.xsd", + + /** + * Constructor: OpenLayers.Format.Filter.v1_1_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.v3.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 matchCase = node.getAttribute("matchCase"); + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.EQUAL_TO, + matchCase: !(matchCase === "false" || matchCase === "0") + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "PropertyIsNotEqualTo": function(node, obj) { + var matchCase = node.getAttribute("matchCase"); + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO, + matchCase: !(matchCase === "false" || matchCase === "0") + }); + 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("escapeChar"); + filter.value2regex(wildCard, singleChar, esc); + obj.filters.push(filter); + } + }, OpenLayers.Format.Filter.v1.prototype.readers["ogc"]), + "gml": OpenLayers.Format.GML.v3.prototype.readers["gml"], + "feature": OpenLayers.Format.GML.v3.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", { + attributes: {matchCase: filter.matchCase} + }); + // 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", { + attributes: {matchCase: filter.matchCase} + }); + // 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: { + matchCase: filter.matchCase, + wildCard: "*", singleChar: ".", escapeChar: "!" + } + }); + // 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 optional in 1.1.0 + filter.property && this.writeNode("PropertyName", filter, node); + var box = this.writeNode("gml:Envelope", filter.value); + if(filter.projection) { + box.setAttribute("srsName", filter.projection); + } + node.appendChild(box); + return node; + }, + "SortBy": function(sortProperties) { + var node = this.createElementNSPlus("ogc:SortBy"); + for (var i=0,l=sortProperties.length;i} 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:Envelope", filter.value); + } + if(filter.projection) { + child.setAttribute("srsName", filter.projection); + } + node.appendChild(child); + } + return node; + }, + + CLASS_NAME: "OpenLayers.Format.Filter.v1_1_0" + +}); +/* ====================================================================== + OpenLayers/Format/OWSCommon.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 + */ + +/** + * Class: OpenLayers.Format.OWSCommon + * Read OWSCommon. Create a new instance with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Format.OWSCommon = 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", + + /** + * Constructor: OpenLayers.Format.OWSCommon + * Create a new parser for OWSCommon. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + + /** + * 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 = this.version; + if(!version) { + // remember version does not correspond to the OWS version + // it corresponds to the WMS/WFS/WCS etc. request version + var uri = root.getAttribute("xmlns:ows"); + // the above will fail if the namespace prefix is different than + // ows and if the namespace is declared on a different element + if (uri && uri.substring(uri.lastIndexOf("/")+1) === "1.1") { + version ="1.1.0"; + } + if(!version) { + version = this.defaultVersion; + } + } + return version; + }, + + /** + * APIMethod: read + * Read an OWSCommon document and return an object. + * + * Parameters: + * data - {String | DOMElement} Data to read. + * options - {Object} Options for the reader. + * + * Returns: + * {Object} An object representing the structure of the document. + */ + + CLASS_NAME: "OpenLayers.Format.OWSCommon" +}); +/* ====================================================================== + OpenLayers/Format/OWSCommon/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/OWSCommon.js + */ + +/** + * Class: OpenLayers.Format.OWSCommon.v1 + * Common readers and writers for OWSCommon v1.X formats + * + * Inherits from: + * - + */ +OpenLayers.Format.OWSCommon.v1 = OpenLayers.Class(OpenLayers.Format.XML, { + + /** + * Property: regExes + * Compiled regular expressions for manipulating strings. + */ + regExes: { + trimSpace: (/^\s*|\s*$/g), + removeSpace: (/\s*/g), + splitSpace: (/\s+/), + trimComma: (/\s*,\s*/g) + }, + + /** + * Method: read + * + * Parameters: + * data - {DOMElement} An OWSCommon document element. + * options - {Object} Options for the reader. + * + * Returns: + * {Object} An object representing the OWSCommon document. + */ + read: function(data, options) { + options = OpenLayers.Util.applyDefaults(options, this.options); + var ows = {}; + this.readChildNodes(data, ows); + return ows; + }, + + /** + * 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: { + "ows": { + "Exception": function(node, exceptionReport) { + var exception = { + code: node.getAttribute('exceptionCode'), + locator: node.getAttribute('locator'), + texts: [] + }; + exceptionReport.exceptions.push(exception); + this.readChildNodes(node, exception); + }, + "ExceptionText": function(node, exception) { + var text = this.getChildValue(node); + exception.texts.push(text); + }, + "ServiceIdentification": function(node, obj) { + obj.serviceIdentification = {}; + this.readChildNodes(node, obj.serviceIdentification); + }, + "Title": function(node, obj) { + obj.title = this.getChildValue(node); + }, + "Abstract": function(node, serviceIdentification) { + serviceIdentification["abstract"] = this.getChildValue(node); + }, + "Keywords": function(node, serviceIdentification) { + serviceIdentification.keywords = {}; + this.readChildNodes(node, serviceIdentification.keywords); + }, + "Keyword": function(node, keywords) { + keywords[this.getChildValue(node)] = true; + }, + "ServiceType": function(node, serviceIdentification) { + serviceIdentification.serviceType = { + codeSpace: node.getAttribute('codeSpace'), + value: this.getChildValue(node)}; + }, + "ServiceTypeVersion": function(node, serviceIdentification) { + serviceIdentification.serviceTypeVersion = this.getChildValue(node); + }, + "Fees": function(node, serviceIdentification) { + serviceIdentification.fees = this.getChildValue(node); + }, + "AccessConstraints": function(node, serviceIdentification) { + serviceIdentification.accessConstraints = + this.getChildValue(node); + }, + "ServiceProvider": function(node, obj) { + obj.serviceProvider = {}; + this.readChildNodes(node, obj.serviceProvider); + }, + "ProviderName": function(node, serviceProvider) { + serviceProvider.providerName = this.getChildValue(node); + }, + "ProviderSite": function(node, serviceProvider) { + serviceProvider.providerSite = this.getAttributeNS(node, + this.namespaces.xlink, "href"); + }, + "ServiceContact": function(node, serviceProvider) { + serviceProvider.serviceContact = {}; + this.readChildNodes(node, serviceProvider.serviceContact); + }, + "IndividualName": function(node, serviceContact) { + serviceContact.individualName = this.getChildValue(node); + }, + "PositionName": function(node, serviceContact) { + serviceContact.positionName = this.getChildValue(node); + }, + "ContactInfo": function(node, serviceContact) { + serviceContact.contactInfo = {}; + this.readChildNodes(node, serviceContact.contactInfo); + }, + "Phone": function(node, contactInfo) { + contactInfo.phone = {}; + this.readChildNodes(node, contactInfo.phone); + }, + "Voice": function(node, phone) { + phone.voice = this.getChildValue(node); + }, + "Address": function(node, contactInfo) { + contactInfo.address = {}; + this.readChildNodes(node, contactInfo.address); + }, + "DeliveryPoint": function(node, address) { + address.deliveryPoint = this.getChildValue(node); + }, + "City": function(node, address) { + address.city = this.getChildValue(node); + }, + "AdministrativeArea": function(node, address) { + address.administrativeArea = this.getChildValue(node); + }, + "PostalCode": function(node, address) { + address.postalCode = this.getChildValue(node); + }, + "Country": function(node, address) { + address.country = this.getChildValue(node); + }, + "ElectronicMailAddress": function(node, address) { + address.electronicMailAddress = this.getChildValue(node); + }, + "Role": function(node, serviceContact) { + serviceContact.role = this.getChildValue(node); + }, + "OperationsMetadata": function(node, obj) { + obj.operationsMetadata = {}; + this.readChildNodes(node, obj.operationsMetadata); + }, + "Operation": function(node, operationsMetadata) { + var name = node.getAttribute("name"); + operationsMetadata[name] = {}; + this.readChildNodes(node, operationsMetadata[name]); + }, + "DCP": function(node, operation) { + operation.dcp = {}; + this.readChildNodes(node, operation.dcp); + }, + "HTTP": function(node, dcp) { + dcp.http = {}; + this.readChildNodes(node, dcp.http); + }, + "Get": function(node, http) { + if (!http.get) { + http.get = []; + } + var obj = { + url: this.getAttributeNS(node, this.namespaces.xlink, "href") + }; + this.readChildNodes(node, obj); + http.get.push(obj); + }, + "Post": function(node, http) { + if (!http.post) { + http.post = []; + } + var obj = { + url: this.getAttributeNS(node, this.namespaces.xlink, "href") + }; + this.readChildNodes(node, obj); + http.post.push(obj); + }, + "Parameter": function(node, operation) { + if (!operation.parameters) { + operation.parameters = {}; + } + var name = node.getAttribute("name"); + operation.parameters[name] = {}; + this.readChildNodes(node, operation.parameters[name]); + }, + "Constraint": function(node, obj) { + if (!obj.constraints) { + obj.constraints = {}; + } + var name = node.getAttribute("name"); + obj.constraints[name] = {}; + this.readChildNodes(node, obj.constraints[name]); + }, + "Value": function(node, allowedValues) { + allowedValues[this.getChildValue(node)] = true; + }, + "OutputFormat": function(node, obj) { + obj.formats.push({value: this.getChildValue(node)}); + this.readChildNodes(node, obj); + }, + "WGS84BoundingBox": function(node, obj) { + var boundingBox = {}; + boundingBox.crs = node.getAttribute("crs"); + if (obj.BoundingBox) { + obj.BoundingBox.push(boundingBox); + } else { + obj.projection = boundingBox.crs; + boundingBox = obj; + } + this.readChildNodes(node, boundingBox); + }, + "BoundingBox": function(node, obj) { + // FIXME: We consider that BoundingBox is the same as WGS84BoundingBox + // LowerCorner = "min_x min_y" + // UpperCorner = "max_x max_y" + // It should normally depend on the projection + this.readers['ows']['WGS84BoundingBox'].apply(this, [node, obj]); + }, + "LowerCorner": 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); + obj.left = pointList[0]; + obj.bottom = pointList[1]; + }, + "UpperCorner": 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); + obj.right = pointList[0]; + obj.top = pointList[1]; + obj.bounds = new OpenLayers.Bounds(obj.left, obj.bottom, + obj.right, obj.top); + delete obj.left; + delete obj.bottom; + delete obj.right; + delete obj.top; + }, + "Language": function(node, obj) { + obj.language = this.getChildValue(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: { + "ows": { + "BoundingBox": function(options, nodeName) { + var node = this.createElementNSPlus(nodeName || "ows:BoundingBox", { + attributes: { + crs: options.projection + } + }); + this.writeNode("ows:LowerCorner", options, node); + this.writeNode("ows:UpperCorner", options, node); + return node; + }, + "LowerCorner": function(options) { + var node = this.createElementNSPlus("ows:LowerCorner", { + value: options.bounds.left + " " + options.bounds.bottom }); + return node; + }, + "UpperCorner": function(options) { + var node = this.createElementNSPlus("ows:UpperCorner", { + value: options.bounds.right + " " + options.bounds.top }); + return node; + }, + "Identifier": function(identifier) { + var node = this.createElementNSPlus("ows:Identifier", { + value: identifier }); + return node; + }, + "Title": function(title) { + var node = this.createElementNSPlus("ows:Title", { + value: title }); + return node; + }, + "Abstract": function(abstractValue) { + var node = this.createElementNSPlus("ows:Abstract", { + value: abstractValue }); + return node; + }, + "OutputFormat": function(format) { + var node = this.createElementNSPlus("ows:OutputFormat", { + value: format }); + return node; + } + } + }, + + CLASS_NAME: "OpenLayers.Format.OWSCommon.v1" + +}); +/* ====================================================================== + OpenLayers/Format/OWSCommon/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/OWSCommon/v1.js + */ + +/** + * Class: OpenLayers.Format.OWSCommon.v1_0_0 + * Parser for OWS Common version 1.0.0. + * + * Inherits from: + * - + */ +OpenLayers.Format.OWSCommon.v1_0_0 = OpenLayers.Class(OpenLayers.Format.OWSCommon.v1, { + + /** + * Property: namespaces + * {Object} Mapping of namespace aliases to namespace URIs. + */ + namespaces: { + ows: "http://www.opengis.net/ows", + xlink: "http://www.w3.org/1999/xlink" + }, + + /** + * 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: { + "ows": OpenLayers.Util.applyDefaults({ + "ExceptionReport": function(node, obj) { + obj.success = false; + obj.exceptionReport = { + version: node.getAttribute('version'), + language: node.getAttribute('language'), + exceptions: [] + }; + this.readChildNodes(node, obj.exceptionReport); + } + }, OpenLayers.Format.OWSCommon.v1.prototype.readers.ows) + }, + + /** + * 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: { + "ows": OpenLayers.Format.OWSCommon.v1.prototype.writers.ows + }, + + CLASS_NAME: "OpenLayers.Format.OWSCommon.v1_0_0" + +}); +/* ====================================================================== + OpenLayers/Format/WFST/v1_1_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_1_0.js + * @requires OpenLayers/Format/OWSCommon/v1_0_0.js + */ + +/** + * Class: OpenLayers.Format.WFST.v1_1_0 + * A format for creating WFS v1.1.0 transactions. Create a new instance with the + * constructor. + * + * Inherits from: + * - + * - + */ +OpenLayers.Format.WFST.v1_1_0 = OpenLayers.Class( + OpenLayers.Format.Filter.v1_1_0, OpenLayers.Format.WFST.v1, { + + /** + * Property: version + * {String} WFS version number. + */ + version: "1.1.0", + + /** + * Property: schemaLocations + * {Object} Properties are namespace aliases, values are schema locations. + */ + schemaLocations: { + "wfs": "http://schemas.opengis.net/wfs/1.1.0/wfs.xsd" + }, + + /** + * Constructor: OpenLayers.Format.WFST.v1_1_0 + * A class for parsing and generating WFS v1.1.0 transactions. + * + * To read additional information like hit count (numberOfFeatures) from + * the FeatureCollection, call the method + * with {output: "object"} as 2nd argument. Note that it is possible to + * just request the hit count from a WFS 1.1.0 server with the + * resultType="hits" request parameter. + * + * 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_1_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.v3. 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.v3.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({ + "FeatureCollection": function(node, obj) { + obj.numberOfFeatures = parseInt(node.getAttribute( + "numberOfFeatures")); + OpenLayers.Format.WFST.v1.prototype.readers["wfs"]["FeatureCollection"].apply( + this, arguments); + }, + "TransactionResponse": function(node, obj) { + obj.insertIds = []; + obj.success = false; + this.readChildNodes(node, obj); + }, + "TransactionSummary": function(node, obj) { + // this is a limited test of success + obj.success = true; + }, + "InsertResults": function(node, obj) { + this.readChildNodes(node, obj); + }, + "Feature": function(node, container) { + var obj = {fids: []}; + this.readChildNodes(node, obj); + container.insertIds.push(obj.fids[0]); + } + }, OpenLayers.Format.WFST.v1.prototype.readers["wfs"]), + "gml": OpenLayers.Format.GML.v3.prototype.readers["gml"], + "feature": OpenLayers.Format.GML.v3.prototype.readers["feature"], + "ogc": OpenLayers.Format.Filter.v1_1_0.prototype.readers["ogc"], + "ows": OpenLayers.Format.OWSCommon.v1_0_0.prototype.readers["ows"] + }, + + /** + * 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({ + "GetFeature": function(options) { + var node = OpenLayers.Format.WFST.v1.prototype.writers["wfs"]["GetFeature"].apply(this, arguments); + options && this.setAttributes(node, { + resultType: options.resultType, + startIndex: options.startIndex, + count: options.count + }); + return node; + }, + "Query": function(options) { + options = OpenLayers.Util.extend({ + featureNS: this.featureNS, + featurePrefix: this.featurePrefix, + featureType: this.featureType, + srsName: this.srsName + }, options); + var prefix = options.featurePrefix; + var node = this.createElementNSPlus("wfs:Query", { + attributes: { + typeName: (prefix ? prefix + ":" : "") + + options.featureType, + srsName: options.srsName + } + }); + if(options.featureNS) { + node.setAttribute("xmlns:" + prefix, options.featureNS); + } + if(options.propertyNames) { + for(var i=0,len = options.propertyNames.length; i} 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/Format/JSON.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. */ + +/** + * Note: + * This work draws heavily from the public domain JSON serializer/deserializer + * at http://www.json.org/json.js. Rewritten so that it doesn't modify + * basic data prototypes. + */ + +/** + * @requires OpenLayers/Format.js + */ + +/** + * Class: OpenLayers.Format.JSON + * A parser to read/write JSON safely. Create a new instance with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Format.JSON = OpenLayers.Class(OpenLayers.Format, { + + /** + * APIProperty: indent + * {String} For "pretty" printing, the indent string will be used once for + * each indentation level. + */ + indent: " ", + + /** + * APIProperty: space + * {String} For "pretty" printing, the space string will be used after + * the ":" separating a name/value pair. + */ + space: " ", + + /** + * APIProperty: newline + * {String} For "pretty" printing, the newline string will be used at the + * end of each name/value pair or array item. + */ + newline: "\n", + + /** + * Property: level + * {Integer} For "pretty" printing, this is incremented/decremented during + * serialization. + */ + level: 0, + + /** + * Property: pretty + * {Boolean} Serialize with extra whitespace for structure. This is set + * by the method. + */ + pretty: false, + + /** + * Property: nativeJSON + * {Boolean} Does the browser support native json? + */ + nativeJSON: (function() { + return !!(window.JSON && typeof JSON.parse == "function" && typeof JSON.stringify == "function"); + })(), + + /** + * Constructor: OpenLayers.Format.JSON + * Create a new parser for JSON. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + + /** + * APIMethod: read + * Deserialize a json string. + * + * Parameters: + * json - {String} A JSON string + * filter - {Function} A function which will be called for every key and + * value at every level of the final result. Each value will be + * replaced by the result of the filter function. This can be used to + * reform generic objects into instances of classes, or to transform + * date strings into Date objects. + * + * Returns: + * {Object} An object, array, string, or number . + */ + read: function(json, filter) { + var object; + if (this.nativeJSON) { + object = JSON.parse(json, filter); + } else try { + /** + * Parsing happens in three stages. In the first stage, we run the + * text against a regular expression which looks for non-JSON + * characters. We are especially concerned with '()' and 'new' + * because they can cause invocation, and '=' because it can + * cause mutation. But just to be safe, we will reject all + * unexpected characters. + */ + if (/^[\],:{}\s]*$/.test(json.replace(/\\["\\\/bfnrtu]/g, '@'). + replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). + replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + + /** + * In the second stage we use the eval function to compile the + * text into a JavaScript structure. The '{' operator is + * subject to a syntactic ambiguity in JavaScript - it can + * begin a block or an object literal. We wrap the text in + * parens to eliminate the ambiguity. + */ + object = eval('(' + json + ')'); + + /** + * In the optional third stage, we recursively walk the new + * structure, passing each name/value pair to a filter + * function for possible transformation. + */ + if(typeof filter === 'function') { + function walk(k, v) { + if(v && typeof v === 'object') { + for(var i in v) { + if(v.hasOwnProperty(i)) { + v[i] = walk(i, v[i]); + } + } + } + return filter(k, v); + } + object = walk('', object); + } + } + } catch(e) { + // Fall through if the regexp test fails. + } + + if(this.keepData) { + this.data = object; + } + + return object; + }, + + /** + * APIMethod: write + * Serialize an object into a JSON string. + * + * Parameters: + * value - {String} The object, array, string, number, boolean or date + * to be serialized. + * pretty - {Boolean} Structure the output with newlines and indentation. + * Default is false. + * + * Returns: + * {String} The JSON string representation of the input value. + */ + write: function(value, pretty) { + this.pretty = !!pretty; + var json = null; + var type = typeof value; + if(this.serialize[type]) { + try { + json = (!this.pretty && this.nativeJSON) ? + JSON.stringify(value) : + this.serialize[type].apply(this, [value]); + } catch(err) { + OpenLayers.Console.error("Trouble serializing: " + err); + } + } + return json; + }, + + /** + * Method: writeIndent + * Output an indentation string depending on the indentation level. + * + * Returns: + * {String} An appropriate indentation string. + */ + writeIndent: function() { + var pieces = []; + if(this.pretty) { + for(var i=0; i 0) { + pieces.push(','); + } + pieces.push(this.writeNewline(), this.writeIndent(), json); + } + } + + this.level -= 1; + pieces.push(this.writeNewline(), this.writeIndent(), ']'); + return pieces.join(''); + }, + + /** + * Method: serialize.string + * Transform a string into a JSON string. + * + * Parameters: + * string - {String} The string to be serialized + * + * Returns: + * {String} A JSON string representing the string. + */ + 'string': function(string) { + // If the string contains no control characters, no quote characters, and no + // backslash characters, then we can simply slap some quotes around it. + // Otherwise we must also replace the offending characters with safe + // sequences. + var m = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }; + if(/["\\\x00-\x1f]/.test(string)) { + return '"' + string.replace(/([\x00-\x1f\\"])/g, function(a, b) { + var c = m[b]; + if(c) { + return c; + } + c = b.charCodeAt(); + return '\\u00' + + Math.floor(c / 16).toString(16) + + (c % 16).toString(16); + }) + '"'; + } + return '"' + string + '"'; + }, + + /** + * Method: serialize.number + * Transform a number into a JSON string. + * + * Parameters: + * number - {Number} The number to be serialized. + * + * Returns: + * {String} A JSON string representing the number. + */ + 'number': function(number) { + return isFinite(number) ? String(number) : "null"; + }, + + /** + * Method: serialize.boolean + * Transform a boolean into a JSON string. + * + * Parameters: + * bool - {Boolean} The boolean to be serialized. + * + * Returns: + * {String} A JSON string representing the boolean. + */ + 'boolean': function(bool) { + return String(bool); + }, + + /** + * Method: serialize.object + * Transform a date into a JSON string. + * + * Parameters: + * date - {Date} The date to be serialized. + * + * Returns: + * {String} A JSON string representing the date. + */ + 'date': function(date) { + function format(number) { + // Format integers to have at least two digits. + return (number < 10) ? '0' + number : number; + } + return '"' + date.getFullYear() + '-' + + format(date.getMonth() + 1) + '-' + + format(date.getDate()) + 'T' + + format(date.getHours()) + ':' + + format(date.getMinutes()) + ':' + + format(date.getSeconds()) + '"'; + } + }, + + CLASS_NAME: "OpenLayers.Format.JSON" + +}); +/* ====================================================================== + OpenLayers/Format/GeoJSON.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/JSON.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 + * @requires OpenLayers/Console.js + */ + +/** + * Class: OpenLayers.Format.GeoJSON + * Read and write GeoJSON. Create a new parser with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Format.GeoJSON = OpenLayers.Class(OpenLayers.Format.JSON, { + + /** + * APIProperty: ignoreExtraDims + * {Boolean} Ignore dimensions higher than 2 when reading geometry + * coordinates. + */ + ignoreExtraDims: false, + + /** + * Constructor: OpenLayers.Format.GeoJSON + * Create a new parser for GeoJSON. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + + /** + * APIMethod: read + * Deserialize a GeoJSON string. + * + * Parameters: + * json - {String} A GeoJSON string + * type - {String} Optional string that determines the structure of + * the output. Supported values are "Geometry", "Feature", and + * "FeatureCollection". If absent or null, a default of + * "FeatureCollection" is assumed. + * filter - {Function} A function which will be called for every key and + * value at every level of the final result. Each value will be + * replaced by the result of the filter function. This can be used to + * reform generic objects into instances of classes, or to transform + * date strings into Date objects. + * + * Returns: + * {Object} The return depends on the value of the type argument. If type + * is "FeatureCollection" (the default), the return will be an array + * of . If type is "Geometry", the input json + * must represent a single geometry, and the return will be an + * . If type is "Feature", the input json must + * represent a single feature, and the return will be an + * . + */ + read: function(json, type, filter) { + type = (type) ? type : "FeatureCollection"; + var results = null; + var obj = null; + if (typeof json == "string") { + obj = OpenLayers.Format.JSON.prototype.read.apply(this, + [json, filter]); + } else { + obj = json; + } + if(!obj) { + OpenLayers.Console.error("Bad JSON: " + json); + } else if(typeof(obj.type) != "string") { + OpenLayers.Console.error("Bad GeoJSON - no type: " + json); + } else if(this.isValidType(obj, type)) { + switch(type) { + case "Geometry": + try { + results = this.parseGeometry(obj); + } catch(err) { + OpenLayers.Console.error(err); + } + break; + case "Feature": + try { + results = this.parseFeature(obj); + results.type = "Feature"; + } catch(err) { + OpenLayers.Console.error(err); + } + break; + case "FeatureCollection": + // for type FeatureCollection, we allow input to be any type + results = []; + switch(obj.type) { + case "Feature": + try { + results.push(this.parseFeature(obj)); + } catch(err) { + results = null; + OpenLayers.Console.error(err); + } + break; + case "FeatureCollection": + for(var i=0, len=obj.features.length; i. + * + * Parameters: + * obj - {Object} An object created from a GeoJSON object + * + * Returns: + * {} A feature. + */ + parseFeature: function(obj) { + var feature, geometry, attributes, bbox; + attributes = (obj.properties) ? obj.properties : {}; + bbox = (obj.geometry && obj.geometry.bbox) || obj.bbox; + try { + geometry = this.parseGeometry(obj.geometry); + } catch(err) { + // deal with bad geometries + throw err; + } + feature = new OpenLayers.Feature.Vector(geometry, attributes); + if(bbox) { + feature.bounds = OpenLayers.Bounds.fromArray(bbox); + } + if(obj.id) { + feature.fid = obj.id; + } + return feature; + }, + + /** + * Method: parseGeometry + * Convert a geometry object from GeoJSON into an . + * + * Parameters: + * obj - {Object} An object created from a GeoJSON object + * + * Returns: + * {} A geometry. + */ + parseGeometry: function(obj) { + if (obj == null) { + return null; + } + var geometry, collection = false; + if(obj.type == "GeometryCollection") { + if(!(OpenLayers.Util.isArray(obj.geometries))) { + throw "GeometryCollection must have geometries array: " + obj; + } + var numGeom = obj.geometries.length; + var components = new Array(numGeom); + for(var i=0; i. + * + * Parameters: + * array - {Object} The coordinates array from the GeoJSON fragment. + * + * Returns: + * {} A geometry. + */ + "point": function(array) { + if (this.ignoreExtraDims == false && + array.length != 2) { + throw "Only 2D points are supported: " + array; + } + return new OpenLayers.Geometry.Point(array[0], array[1]); + }, + + /** + * Method: parseCoords.multipoint + * Convert a coordinate array from GeoJSON into an + * . + * + * Parameters: + * array - {Object} The coordinates array from the GeoJSON fragment. + * + * Returns: + * {} A geometry. + */ + "multipoint": function(array) { + var points = []; + var p = null; + for(var i=0, len=array.length; i. + * + * Parameters: + * array - {Object} The coordinates array from the GeoJSON fragment. + * + * Returns: + * {} A geometry. + */ + "linestring": function(array) { + var points = []; + var p = null; + for(var i=0, len=array.length; i. + * + * Parameters: + * array - {Object} The coordinates array from the GeoJSON fragment. + * + * Returns: + * {} A geometry. + */ + "multilinestring": function(array) { + var lines = []; + var l = null; + for(var i=0, len=array.length; i. + * + * Returns: + * {} A geometry. + */ + "polygon": function(array) { + var rings = []; + var r, l; + for(var i=0, len=array.length; i. + * + * Parameters: + * array - {Object} The coordinates array from the GeoJSON fragment. + * + * Returns: + * {} A geometry. + */ + "multipolygon": function(array) { + var polys = []; + var p = null; + for(var i=0, len=array.length; i. + * + * Parameters: + * array - {Object} The coordinates array from the GeoJSON fragment. + * + * Returns: + * {} A geometry. + */ + "box": function(array) { + if(array.length != 2) { + throw "GeoJSON box coordinates must have 2 elements"; + } + return new OpenLayers.Geometry.Polygon([ + new OpenLayers.Geometry.LinearRing([ + new OpenLayers.Geometry.Point(array[0][0], array[0][1]), + new OpenLayers.Geometry.Point(array[1][0], array[0][1]), + new OpenLayers.Geometry.Point(array[1][0], array[1][1]), + new OpenLayers.Geometry.Point(array[0][0], array[1][1]), + new OpenLayers.Geometry.Point(array[0][0], array[0][1]) + ]) + ]); + } + + }, + + /** + * APIMethod: write + * Serialize a feature, geometry, array of features into a GeoJSON string. + * + * Parameters: + * obj - {Object} An , , + * or an array of features. + * pretty - {Boolean} Structure the output with newlines and indentation. + * Default is false. + * + * Returns: + * {String} The GeoJSON string representation of the input geometry, + * features, or array of features. + */ + write: function(obj, pretty) { + var geojson = { + "type": null + }; + if(OpenLayers.Util.isArray(obj)) { + geojson.type = "FeatureCollection"; + var numFeatures = obj.length; + geojson.features = new Array(numFeatures); + for(var i=0; i} + * + * Returns: + * {Object} An object which can be assigned to the crs property + * of a GeoJSON object. + */ + createCRSObject: function(object) { + var proj = object.layer.projection.toString(); + var crs = {}; + if (proj.match(/epsg:/i)) { + var code = parseInt(proj.substring(proj.indexOf(":") + 1)); + if (code == 4326) { + crs = { + "type": "name", + "properties": { + "name": "urn:ogc:def:crs:OGC:1.3:CRS84" + } + }; + } else { + crs = { + "type": "name", + "properties": { + "name": "EPSG:" + code + } + }; + } + } + return crs; + }, + + /** + * Property: extract + * Object with properties corresponding to the GeoJSON types. + * Property values are functions that do the actual value extraction. + */ + extract: { + /** + * Method: extract.feature + * Return a partial GeoJSON object representing a single feature. + * + * Parameters: + * feature - {} + * + * Returns: + * {Object} An object representing the point. + */ + 'feature': function(feature) { + var geom = this.extract.geometry.apply(this, [feature.geometry]); + var json = { + "type": "Feature", + "properties": feature.attributes, + "geometry": geom + }; + if (feature.fid != null) { + json.id = feature.fid; + } + return json; + }, + + /** + * Method: extract.geometry + * Return a GeoJSON object representing a single geometry. + * + * Parameters: + * geometry - {} + * + * Returns: + * {Object} An object representing the geometry. + */ + 'geometry': function(geometry) { + if (geometry == null) { + return null; + } + if (this.internalProjection && this.externalProjection) { + geometry = geometry.clone(); + geometry.transform(this.internalProjection, + this.externalProjection); + } + var geometryType = geometry.CLASS_NAME.split('.')[2]; + var data = this.extract[geometryType.toLowerCase()].apply(this, [geometry]); + var json; + if(geometryType == "Collection") { + json = { + "type": "GeometryCollection", + "geometries": data + }; + } else { + json = { + "type": geometryType, + "coordinates": data + }; + } + + return json; + }, + + /** + * Method: extract.point + * Return an array of coordinates from a point. + * + * Parameters: + * point - {} + * + * Returns: + * {Array} An array of coordinates representing the point. + */ + 'point': function(point) { + return [point.x, point.y]; + }, + + /** + * Method: extract.multipoint + * Return an array of point coordinates from a multipoint. + * + * Parameters: + * multipoint - {} + * + * Returns: + * {Array} An array of point coordinate arrays representing + * the multipoint. + */ + 'multipoint': function(multipoint) { + var array = []; + for(var i=0, len=multipoint.components.length; i} + * + * Returns: + * {Array} An array of coordinate arrays representing + * the linestring. + */ + 'linestring': function(linestring) { + var array = []; + for(var i=0, len=linestring.components.length; i} + * + * Returns: + * {Array} An array of linestring arrays representing + * the multilinestring. + */ + 'multilinestring': function(multilinestring) { + var array = []; + for(var i=0, len=multilinestring.components.length; i} + * + * Returns: + * {Array} An array of linear ring arrays representing the polygon. + */ + 'polygon': function(polygon) { + var array = []; + for(var i=0, len=polygon.components.length; i} + * + * Returns: + * {Array} An array of polygon arrays representing + * the multipolygon + */ + 'multipolygon': function(multipolygon) { + var array = []; + for(var i=0, len=multipolygon.components.length; i} + * + * Returns: + * {Array} An array of geometry objects representing the geometry + * collection. + */ + 'collection': function(collection) { + var len = collection.components.length; + var array = new Array(len); + for(var i=0; i constructor. A script protocol is used to + * get around the same origin policy. It works with services that return + * JSONP - that is, JSON wrapped in a client-specified callback. The + * protocol handles fetching and parsing of feature data and sends parsed + * features to the configured with the protocol. The protocol + * expects features serialized as GeoJSON by default, but can be configured + * to work with other formats by setting the property. + * + * Inherits from: + * - + */ +OpenLayers.Protocol.Script = OpenLayers.Class(OpenLayers.Protocol, { + + /** + * APIProperty: url + * {String} Service URL. The service is expected to return serialized + * features wrapped in a named callback (where the callback name is + * generated by this protocol). + * Read-only, set through the options passed to the constructor. + */ + url: null, + + /** + * APIProperty: params + * {Object} Query string parameters to be appended to the URL. + * Read-only, set through the options passed to the constructor. + * Example: {maxFeatures: 50} + */ + params: null, + + /** + * APIProperty: callback + * {Object} Function to be called when the operation completes. + */ + callback: null, + + /** + * APIProperty: callbackTemplate + * {String} Template for creating a unique callback function name + * for the registry. Should include ${id}. The ${id} variable will be + * replaced with a string identifier prefixed with a "c" (e.g. c1, c2). + * Default is "OpenLayers.Protocol.Script.registry.${id}". + */ + callbackTemplate: "OpenLayers.Protocol.Script.registry.${id}", + + /** + * APIProperty: callbackKey + * {String} The name of the query string parameter that the service + * recognizes as the callback identifier. Default is "callback". + * This key is used to generate the URL for the script. For example + * setting to "myCallback" would result in a URL like + * http://example.com/?myCallback=... + */ + callbackKey: "callback", + + /** + * APIProperty: callbackPrefix + * {String} Where a service requires that the callback query string + * parameter value is prefixed by some string, this value may be set. + * For example, setting to "foo:" would result in a + * URL like http://example.com/?callback=foo:... Default is "". + */ + callbackPrefix: "", + + /** + * APIProperty: scope + * {Object} Optional ``this`` object for the callback. Read-only, set + * through the options passed to the constructor. + */ + scope: null, + + /** + * APIProperty: format + * {} Format for parsing features. Default is an + * format. If an alternative is provided, + * the format's read method must take an object and return an array + * of features. + */ + format: null, + + /** + * Property: pendingRequests + * {Object} References all pending requests. Property names are script + * identifiers and property values are script elements. + */ + pendingRequests: null, + + /** + * APIProperty: srsInBBOX + * {Boolean} Include the SRS identifier in BBOX query string parameter. + * Setting this property has no effect if a custom filterToParams method + * is provided. Default is false. If true and the layer has a + * projection object set, any BBOX filter will be serialized with a + * fifth item identifying the projection. + * E.g. bbox=-1000,-1000,1000,1000,EPSG:900913 + */ + srsInBBOX: false, + + /** + * Constructor: OpenLayers.Protocol.Script + * A class for giving layers generic Script protocol. + * + * Parameters: + * options - {Object} Optional object whose properties will be set on the + * instance. + * + * Valid options include: + * url - {String} + * params - {Object} + * callback - {Function} + * scope - {Object} + */ + initialize: function(options) { + options = options || {}; + this.params = {}; + this.pendingRequests = {}; + OpenLayers.Protocol.prototype.initialize.apply(this, arguments); + if (!this.format) { + this.format = new OpenLayers.Format.GeoJSON(); + } + + if (!this.filterToParams && OpenLayers.Format.QueryStringFilter) { + var format = new OpenLayers.Format.QueryStringFilter({ + srsInBBOX: this.srsInBBOX + }); + this.filterToParams = function(filter, params) { + return format.write(filter, params); + }; + } + }, + + /** + * APIMethod: read + * Construct a request for reading new features. + * + * Parameters: + * options - {Object} Optional object for configuring the request. + * This object is modified and should not be reused. + * + * Valid options: + * url - {String} Url for the request. + * params - {Object} Parameters to get serialized as a query string. + * filter - {} Filter to get serialized as a + * query string. + * + * Returns: + * {} A response object, whose "priv" property + * references the injected script. This object is also passed to the + * callback function when the request completes, its "features" property + * is then populated with the features received from the server. + */ + read: function(options) { + OpenLayers.Protocol.prototype.read.apply(this, arguments); + options = OpenLayers.Util.applyDefaults(options, this.options); + options.params = OpenLayers.Util.applyDefaults( + options.params, this.options.params + ); + if (options.filter && this.filterToParams) { + options.params = this.filterToParams( + options.filter, options.params + ); + } + var response = new OpenLayers.Protocol.Response({requestType: "read"}); + var request = this.createRequest( + options.url, + options.params, + OpenLayers.Function.bind(function(data) { + response.data = data; + this.handleRead(response, options); + }, this) + ); + response.priv = request; + return response; + }, + + /** + * APIMethod: filterToParams + * Optional method to translate an object into an object + * that can be serialized as request query string provided. If a custom + * method is not provided, any filter will not be serialized. + * + * Parameters: + * filter - {} filter to convert. + * params - {Object} The parameters object. + * + * Returns: + * {Object} The resulting parameters object. + */ + + /** + * Method: createRequest + * Issues a request for features by creating injecting a script in the + * document head. + * + * Parameters: + * url - {String} Service URL. + * params - {Object} Query string parameters. + * callback - {Function} Callback to be called with resulting data. + * + * Returns: + * {HTMLScriptElement} The script pending execution. + */ + createRequest: function(url, params, callback) { + var id = OpenLayers.Protocol.Script.register(callback); + var name = OpenLayers.String.format(this.callbackTemplate, {id: id}); + params = OpenLayers.Util.extend({}, params); + params[this.callbackKey] = this.callbackPrefix + name; + url = OpenLayers.Util.urlAppend( + url, OpenLayers.Util.getParameterString(params) + ); + var script = document.createElement("script"); + script.type = "text/javascript"; + script.src = url; + script.id = "OpenLayers_Protocol_Script_" + id; + this.pendingRequests[script.id] = script; + var head = document.getElementsByTagName("head")[0]; + head.appendChild(script); + return script; + }, + + /** + * Method: destroyRequest + * Remove a script node associated with a response from the document. Also + * unregisters the callback and removes the script from the + * object. + * + * Parameters: + * script - {HTMLScriptElement} + */ + destroyRequest: function(script) { + OpenLayers.Protocol.Script.unregister(script.id.split("_").pop()); + delete this.pendingRequests[script.id]; + if (script.parentNode) { + script.parentNode.removeChild(script); + } + }, + + /** + * Method: handleRead + * Individual callbacks are created for read, create and update, should + * a subclass need to override each one separately. + * + * Parameters: + * response - {} The response object to pass to + * the user callback. + * options - {Object} The user options passed to the read call. + */ + handleRead: function(response, options) { + this.handleResponse(response, options); + }, + + /** + * Method: handleResponse + * Called by CRUD specific handlers. + * + * Parameters: + * response - {} The response object to pass to + * any user callback. + * options - {Object} The user options passed to the create, read, update, + * or delete call. + */ + handleResponse: function(response, options) { + if (options.callback) { + if (response.data) { + response.features = this.parseFeatures(response.data); + response.code = OpenLayers.Protocol.Response.SUCCESS; + } else { + response.code = OpenLayers.Protocol.Response.FAILURE; + } + this.destroyRequest(response.priv); + options.callback.call(options.scope, response); + } + }, + + /** + * Method: parseFeatures + * Read Script response body and return features. + * + * Parameters: + * data - {Object} The data sent to the callback function by the server. + * + * Returns: + * {Array({})} or + * {} Array of features or a single feature. + */ + parseFeatures: function(data) { + return this.format.read(data); + }, + + /** + * APIMethod: abort + * Abort an ongoing request. If no response is provided, all pending + * requests will be aborted. + * + * Parameters: + * response - {} The response object returned + * from a request. + */ + abort: function(response) { + if (response) { + this.destroyRequest(response.priv); + } else { + for (var key in this.pendingRequests) { + this.destroyRequest(this.pendingRequests[key]); + } + } + }, + + /** + * APIMethod: destroy + * Clean up the protocol. + */ + destroy: function() { + this.abort(); + delete this.params; + delete this.format; + OpenLayers.Protocol.prototype.destroy.apply(this); + }, + + CLASS_NAME: "OpenLayers.Protocol.Script" +}); + +(function() { + var o = OpenLayers.Protocol.Script; + var counter = 0; + o.registry = {}; + + /** + * Function: OpenLayers.Protocol.Script.register + * Register a callback for a newly created script. + * + * Parameters: + * callback - {Function} The callback to be executed when the newly added + * script loads. This callback will be called with a single argument + * that is the JSON returned by the service. + * + * Returns: + * {Number} An identifier for retrieving the registered callback. + */ + o.register = function(callback) { + var id = "c"+(++counter); + o.registry[id] = function() { + callback.apply(this, arguments); + }; + return id; + }; + + /** + * Function: OpenLayers.Protocol.Script.unregister + * Unregister a callback previously registered with the register function. + * + * Parameters: + * id - {Number} The identifer returned by the register function. + */ + o.unregister = function(id) { + delete o.registry[id]; + }; +})(); +/* ====================================================================== + OpenLayers/Format/EncodedPolyline.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 + * @requires OpenLayers/Feature/Vector.js + */ + +/** + * Class: OpenLayers.Format.EncodedPolyline + * Class for reading and writing encoded polylines. Create a new instance + * with the constructor. + * + * Inherits from: + * - + */ +OpenLayers.Format.EncodedPolyline = OpenLayers.Class(OpenLayers.Format, { + + /** + * APIProperty: geometryType + * {String} Geometry type to output. One of: linestring (default), + * linearring, point, multipoint or polygon. If the geometryType is + * point, only the first point of the string is returned. + */ + geometryType: "linestring", + + /** + * Constructor: OpenLayers.Format.EncodedPolyline + * Create a new parser for encoded polylines + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance + * + * Returns: + * {} A new encoded polylines parser. + */ + initialize: function(options) { + OpenLayers.Format.prototype.initialize.apply(this, [options]); + }, + + /** + * APIMethod: read + * Deserialize an encoded polyline string and return a vector feature. + * + * Parameters: + * encoded - {String} An encoded polyline string + * + * Returns: + * {} A vector feature with a linestring. + */ + read: function(encoded) { + var geomType; + if (this.geometryType == "linestring") + geomType = OpenLayers.Geometry.LineString; + else if (this.geometryType == "linearring") + geomType = OpenLayers.Geometry.LinearRing; + else if (this.geometryType == "multipoint") + geomType = OpenLayers.Geometry.MultiPoint; + else if (this.geometryType != "point" && this.geometryType != "polygon") + return null; + + var flatPoints = this.decodeDeltas(encoded, 2); + var flatPointsLength = flatPoints.length; + + var pointGeometries = []; + for (var i = 0; i + 1 < flatPointsLength;) { + var y = flatPoints[i++], x = flatPoints[i++]; + pointGeometries.push(new OpenLayers.Geometry.Point(x, y)); + } + + + if (this.geometryType == "point") + return new OpenLayers.Feature.Vector( + pointGeometries[0] + ); + + if (this.geometryType == "polygon") + return new OpenLayers.Feature.Vector( + new OpenLayers.Geometry.Polygon([ + new OpenLayers.Geometry.LinearRing(pointGeometries) + ]) + ); + + return new OpenLayers.Feature.Vector( + new geomType(pointGeometries) + ); + }, + + /** + * APIMethod: decode + * Deserialize an encoded string and return an array of n-dimensional + * points. + * + * Parameters: + * encoded - {String} An encoded string + * dims - {int} The dimension of the points that are returned + * + * Returns: + * {Array(Array(int))} An array containing n-dimensional arrays of + * coordinates. + */ + decode: function(encoded, dims, opt_factor) { + var factor = opt_factor || 1e5; + var flatPoints = this.decodeDeltas(encoded, dims, factor); + var flatPointsLength = flatPoints.length; + + var points = []; + for (var i = 0; i + (dims - 1) < flatPointsLength;) { + var point = []; + + for (var dim = 0; dim < dims; ++dim) { + point.push(flatPoints[i++]) + } + + points.push(point); + } + + return points; + }, + + /** + * APIMethod: write + * Serialize a feature or array of features into a WKT string. + * + * Parameters: + * features - {|Array} A feature or array of + * features + * + * Returns: + * {String} The WKT string representation of the input geometries + */ + write: function(features) { + var feature; + if (features.constructor == Array) + feature = features[0]; + else + feature = features; + + var geometry = feature.geometry; + var type = geometry.CLASS_NAME.split('.')[2].toLowerCase(); + + var pointGeometries; + if (type == "point") + pointGeometries = new Array(geometry); + else if (type == "linestring" || + type == "linearring" || + type == "multipoint") + pointGeometries = geometry.components; + else if (type == "polygon") + pointGeometries = geometry.components[0].components; + else + return null; + + var flatPoints = []; + + var pointGeometriesLength = pointGeometries.length; + for (var i = 0; i < pointGeometriesLength; ++i) { + var pointGeometry = pointGeometries[i]; + flatPoints.push(pointGeometry.y); + flatPoints.push(pointGeometry.x); + } + + return this.encodeDeltas(flatPoints, 2); + }, + + /** + * APIMethod: encode + * Serialize an array of n-dimensional points and return an encoded string + * + * Parameters: + * points - {Array(Array(int))} An array containing n-dimensional + * arrays of coordinates + * dims - {int} The dimension of the points that should be read + * + * Returns: + * {String} An encoded string + */ + encode: function (points, dims, opt_factor) { + var factor = opt_factor || 1e5; + var flatPoints = []; + + var pointsLength = points.length; + for (var i = 0; i < pointsLength; ++i) { + var point = points[i]; + + for (var dim = 0; dim < dims; ++dim) { + flatPoints.push(point[dim]); + } + } + + return this.encodeDeltas(flatPoints, dims, factor); + }, + + /** + * APIMethod: encodeDeltas + * Encode a list of n-dimensional points and return an encoded string + * + * Attention: This function will modify the passed array! + * + * Parameters: + * numbers - {Array.} A list of n-dimensional points. + * dimension - {number} The dimension of the points in the list. + * opt_factor - {number=} The factor by which the numbers will be + * multiplied. The remaining decimal places will get rounded away. + * + * Returns: + * {string} The encoded string. + */ + encodeDeltas: function(numbers, dimension, opt_factor) { + var factor = opt_factor || 1e5; + var d; + + var lastNumbers = new Array(dimension); + for (d = 0; d < dimension; ++d) { + lastNumbers[d] = 0; + } + + var numbersLength = numbers.length; + for (var i = 0; i < numbersLength;) { + for (d = 0; d < dimension; ++d, ++i) { + var num = numbers[i]; + var delta = num - lastNumbers[d]; + lastNumbers[d] = num; + + numbers[i] = delta; + } + } + + return this.encodeFloats(numbers, factor); + }, + + + /** + * APIMethod: decodeDeltas + * Decode a list of n-dimensional points from an encoded string + * + * Parameters: + * encoded - {string} An encoded string. + * dimension - {number} The dimension of the points in the encoded string. + * opt_factor - {number=} The factor by which the resulting numbers will + * be divided. + * + * Returns: + * {Array.} A list of n-dimensional points. + */ + decodeDeltas: function(encoded, dimension, opt_factor) { + var factor = opt_factor || 1e5; + var d; + + var lastNumbers = new Array(dimension); + for (d = 0; d < dimension; ++d) { + lastNumbers[d] = 0; + } + + var numbers = this.decodeFloats(encoded, factor); + + var numbersLength = numbers.length; + for (var i = 0; i < numbersLength;) { + for (d = 0; d < dimension; ++d, ++i) { + lastNumbers[d] += numbers[i]; + + numbers[i] = lastNumbers[d]; + } + } + + return numbers; + }, + + + /** + * APIMethod: encodeFloats + * Encode a list of floating point numbers and return an encoded string + * + * Attention: This function will modify the passed array! + * + * Parameters: + * numbers - {Array.} A list of floating point numbers. + * opt_factor - {number=} The factor by which the numbers will be + * multiplied. The remaining decimal places will get rounded away. + * + * Returns: + * {string} The encoded string. + */ + encodeFloats: function(numbers, opt_factor) { + var factor = opt_factor || 1e5; + + var numbersLength = numbers.length; + for (var i = 0; i < numbersLength; ++i) { + numbers[i] = Math.round(numbers[i] * factor); + } + + return this.encodeSignedIntegers(numbers); + }, + + + /** + * APIMethod: decodeFloats + * Decode a list of floating point numbers from an encoded string + * + * Parameters: + * encoded - {string} An encoded string. + * opt_factor - {number=} The factor by which the result will be divided. + * + * Returns: + * {Array.} A list of floating point numbers. + */ + decodeFloats: function(encoded, opt_factor) { + var factor = opt_factor || 1e5; + + var numbers = this.decodeSignedIntegers(encoded); + + var numbersLength = numbers.length; + for (var i = 0; i < numbersLength; ++i) { + numbers[i] /= factor; + } + + return numbers; + }, + + + /** + * APIMethod: encodeSignedIntegers + * Encode a list of signed integers and return an encoded string + * + * Attention: This function will modify the passed array! + * + * Parameters: + * numbers - {Array.} A list of signed integers. + * + * Returns: + * {string} The encoded string. + */ + encodeSignedIntegers: function(numbers) { + var numbersLength = numbers.length; + for (var i = 0; i < numbersLength; ++i) { + var num = numbers[i]; + + var signedNum = num << 1; + if (num < 0) { + signedNum = ~(signedNum); + } + + numbers[i] = signedNum; + } + + return this.encodeUnsignedIntegers(numbers); + }, + + + /** + * APIMethod: decodeSignedIntegers + * Decode a list of signed integers from an encoded string + * + * Parameters: + * encoded - {string} An encoded string. + * + * Returns: + * {Array.} A list of signed integers. + */ + decodeSignedIntegers: function(encoded) { + var numbers = this.decodeUnsignedIntegers(encoded); + + var numbersLength = numbers.length; + for (var i = 0; i < numbersLength; ++i) { + var num = numbers[i]; + numbers[i] = (num & 1) ? ~(num >> 1) : (num >> 1); + } + + return numbers; + }, + + + /** + * APIMethod: encodeUnsignedIntegers + * Encode a list of unsigned integers and return an encoded string + * + * Parameters: + * numbers - {Array.} A list of unsigned integers. + * + * Returns: + * {string} The encoded string. + */ + encodeUnsignedIntegers: function(numbers) { + var encoded = ''; + + var numbersLength = numbers.length; + for (var i = 0; i < numbersLength; ++i) { + encoded += this.encodeUnsignedInteger(numbers[i]); + } + + return encoded; + }, + + + /** + * APIMethod: decodeUnsignedIntegers + * Decode a list of unsigned integers from an encoded string + * + * Parameters: + * encoded - {string} An encoded string. + * + * Returns: + * {Array.} A list of unsigned integers. + */ + decodeUnsignedIntegers: function(encoded) { + var numbers = []; + + var current = 0; + var shift = 0; + + var encodedLength = encoded.length; + for (var i = 0; i < encodedLength; ++i) { + var b = encoded.charCodeAt(i) - 63; + + current |= (b & 0x1f) << shift; + + if (b < 0x20) { + numbers.push(current); + current = 0; + shift = 0; + } else { + shift += 5; + } + } + + return numbers; + }, + + + /** + * Method: encodeFloat + * Encode one single floating point number and return an encoded string + * + * Parameters: + * num - {number} Floating point number that should be encoded. + * opt_factor - {number=} The factor by which num will be multiplied. + * The remaining decimal places will get rounded away. + * + * Returns: + * {string} The encoded string. + */ + encodeFloat: function(num, opt_factor) { + num = Math.round(num * (opt_factor || 1e5)); + return this.encodeSignedInteger(num); + }, + + + /** + * Method: decodeFloat + * Decode one single floating point number from an encoded string + * + * Parameters: + * encoded - {string} An encoded string. + * opt_factor - {number=} The factor by which the result will be divided. + * + * Returns: + * {number} The decoded floating point number. + */ + decodeFloat: function(encoded, opt_factor) { + var result = this.decodeSignedInteger(encoded); + return result / (opt_factor || 1e5); + }, + + + /** + * Method: encodeSignedInteger + * Encode one single signed integer and return an encoded string + * + * Parameters: + * num - {number} Signed integer that should be encoded. + * + * Returns: + * {string} The encoded string. + */ + encodeSignedInteger: function(num) { + var signedNum = num << 1; + if (num < 0) { + signedNum = ~(signedNum); + } + + return this.encodeUnsignedInteger(signedNum); + }, + + + /** + * Method: decodeSignedInteger + * Decode one single signed integer from an encoded string + * + * Parameters: + * encoded - {string} An encoded string. + * + * Returns: + * {number} The decoded signed integer. + */ + decodeSignedInteger: function(encoded) { + var result = this.decodeUnsignedInteger(encoded); + return ((result & 1) ? ~(result >> 1) : (result >> 1)); + }, + + + /** + * Method: encodeUnsignedInteger + * Encode one single unsigned integer and return an encoded string + * + * Parameters: + * num - {number} Unsigned integer that should be encoded. + * + * Returns: + * {string} The encoded string. + */ + encodeUnsignedInteger: function(num) { + var value, encoded = ''; + while (num >= 0x20) { + value = (0x20 | (num & 0x1f)) + 63; + encoded += (String.fromCharCode(value)); + num >>= 5; + } + value = num + 63; + encoded += (String.fromCharCode(value)); + return encoded; + }, + + + /** + * Method: decodeUnsignedInteger + * Decode one single unsigned integer from an encoded string + * + * Parameters: + * encoded - {string} An encoded string. + * + * Returns: + * {number} The decoded unsigned integer. + */ + decodeUnsignedInteger: function(encoded) { + var result = 0; + var shift = 0; + + var encodedLength = encoded.length; + for (var i = 0; i < encodedLength; ++i) { + var b = encoded.charCodeAt(i) - 63; + + result |= (b & 0x1f) << shift; + + if (b < 0x20) + break; + + shift += 5; + } + + return result; + }, + + CLASS_NAME: "OpenLayers.Format.EncodedPolyline" +}); +/* ====================================================================== + 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/Control/Button.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 + */ + +/** + * Class: OpenLayers.Control.Button + * The Button control is a very simple push-button, for use with + * . + * When clicked, the function trigger() is executed. + * + * Inherits from: + * - + * + * Use: + * (code) + * var button = new OpenLayers.Control.Button({ + * displayClass: "MyButton", trigger: myFunction + * }); + * panel.addControls([button]); + * (end) + * + * Will create a button with CSS class MyButtonItemInactive, that + * will call the function MyFunction() when clicked. + */ +OpenLayers.Control.Button = OpenLayers.Class(OpenLayers.Control, { + /** + * Property: type + * {Integer} OpenLayers.Control.TYPE_BUTTON. + */ + type: OpenLayers.Control.TYPE_BUTTON, + + /** + * Method: trigger + * Called by a control panel when the button is clicked. + */ + trigger: function() {}, + + CLASS_NAME: "OpenLayers.Control.Button" +}); +/* ====================================================================== + OpenLayers/Control/ZoomIn.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/Button.js + */ + +/** + * Class: OpenLayers.Control.ZoomIn + * The ZoomIn control is a button to increase the zoom level of a map. + * + * Inherits from: + * - + */ +OpenLayers.Control.ZoomIn = OpenLayers.Class(OpenLayers.Control.Button, { + + /** + * Method: trigger + */ + trigger: function(){ + if (this.map) { + this.map.zoomIn(); + } + }, + + CLASS_NAME: "OpenLayers.Control.ZoomIn" +}); +/* ====================================================================== + OpenLayers/Control/ZoomOut.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/Button.js + */ + +/** + * Class: OpenLayers.Control.ZoomOut + * The ZoomOut control is a button to decrease the zoom level of a map. + * + * Inherits from: + * - + */ +OpenLayers.Control.ZoomOut = OpenLayers.Class(OpenLayers.Control.Button, { + + /** + * Method: trigger + */ + trigger: function(){ + if (this.map) { + this.map.zoomOut(); + } + }, + + CLASS_NAME: "OpenLayers.Control.ZoomOut" +}); +/* ====================================================================== + OpenLayers/Control/ZoomToMaxExtent.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/Button.js + */ + +/** + * Class: OpenLayers.Control.ZoomToMaxExtent + * The ZoomToMaxExtent control is a button that zooms out to the maximum + * extent of the map. It is designed to be used with a + * . + * + * Inherits from: + * - + */ +OpenLayers.Control.ZoomToMaxExtent = OpenLayers.Class(OpenLayers.Control.Button, { + + /** + * Method: trigger + * + * Called whenever this control is being rendered inside of a panel and a + * click occurs on this controls element. Actually zooms to the maximum + * extent of this controls map. + */ + trigger: function() { + if (this.map) { + this.map.zoomToMaxExtent(); + } + }, + + CLASS_NAME: "OpenLayers.Control.ZoomToMaxExtent" +}); +/* ====================================================================== + OpenLayers/Control/ZoomPanel.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/Panel.js + * @requires OpenLayers/Control/ZoomIn.js + * @requires OpenLayers/Control/ZoomOut.js + * @requires OpenLayers/Control/ZoomToMaxExtent.js + */ + +/** + * Class: OpenLayers.Control.ZoomPanel + * The ZoomPanel control is a compact collecton of 3 zoom controls: a + * , a , and a + * . By default it is drawn in the upper left + * corner of the map. + * + * Note: + * If you wish to use this class with the default images and you want + * it to look nice in ie6, you should add the following, conditionally + * added css stylesheet to your HTML file: + * + * (code) + * + * (end) + * + * Inherits from: + * - + */ +OpenLayers.Control.ZoomPanel = OpenLayers.Class(OpenLayers.Control.Panel, { + + /** + * Constructor: OpenLayers.Control.ZoomPanel + * Add the three zooming controls. + * + * Parameters: + * options - {Object} An optional object whose properties will be used + * to extend the control. + */ + initialize: function(options) { + OpenLayers.Control.Panel.prototype.initialize.apply(this, [options]); + this.addControls([ + new OpenLayers.Control.ZoomIn(), + new OpenLayers.Control.ZoomToMaxExtent(), + new OpenLayers.Control.ZoomOut() + ]); + }, + + CLASS_NAME: "OpenLayers.Control.ZoomPanel" +}); +/* ====================================================================== + 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/Format/ArcXML.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/Geometry/Polygon.js + * @requires OpenLayers/Geometry/Point.js + * @requires OpenLayers/Geometry/MultiPolygon.js + * @requires OpenLayers/Geometry/LinearRing.js + */ + +/** + * Class: OpenLayers.Format.ArcXML + * Read/Write ArcXML. Create a new instance with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Format.ArcXML = OpenLayers.Class(OpenLayers.Format.XML, { + + /** + * Property: fontStyleKeys + * {Array} List of keys used in font styling. + */ + fontStyleKeys: [ + 'antialiasing', 'blockout', 'font', 'fontcolor','fontsize', 'fontstyle', + 'glowing', 'interval', 'outline', 'printmode', 'shadow', 'transparency' + ], + + /** + * Property: request + * A get_image request destined for an ArcIMS server. + */ + request: null, + + /** + * Property: response + * A parsed response from an ArcIMS server. + */ + response: null, + + /** + * Constructor: OpenLayers.Format.ArcXML + * Create a new parser/writer for ArcXML. Create an instance of this class + * to begin authoring a request to an ArcIMS service. This is used + * primarily by the ArcIMS layer, but could be used to do other wild + * stuff, like geocoding. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + initialize: function(options) { + this.request = new OpenLayers.Format.ArcXML.Request(); + this.response = new OpenLayers.Format.ArcXML.Response(); + + if (options) { + if (options.requesttype == "feature") { + this.request.get_image = null; + + var qry = this.request.get_feature.query; + this.addCoordSys(qry.featurecoordsys, options.featureCoordSys); + this.addCoordSys(qry.filtercoordsys, options.filterCoordSys); + + if (options.polygon) { + qry.isspatial = true; + qry.spatialfilter.polygon = options.polygon; + } else if (options.envelope) { + qry.isspatial = true; + qry.spatialfilter.envelope = {minx:0, miny:0, maxx:0, maxy:0}; + this.parseEnvelope(qry.spatialfilter.envelope, options.envelope); + } + } else if (options.requesttype == "image") { + this.request.get_feature = null; + + var props = this.request.get_image.properties; + this.parseEnvelope(props.envelope, options.envelope); + + this.addLayers(props.layerlist, options.layers); + this.addImageSize(props.imagesize, options.tileSize); + this.addCoordSys(props.featurecoordsys, options.featureCoordSys); + this.addCoordSys(props.filtercoordsys, options.filterCoordSys); + } else { + // if an arcxml object is being created with no request type, it is + // probably going to consume a response, so do not throw an error if + // the requesttype is not defined + this.request = null; + } + } + + OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); + }, + + /** + * Method: parseEnvelope + * Parse an array of coordinates into an ArcXML envelope structure. + * + * Parameters: + * env - {Object} An envelope object that will contain the parsed coordinates. + * arr - {Array(double)} An array of coordinates in the order: [ minx, miny, maxx, maxy ] + */ + parseEnvelope: function(env, arr) { + if (arr && arr.length == 4) { + env.minx = arr[0]; + env.miny = arr[1]; + env.maxx = arr[2]; + env.maxy = arr[3]; + } + }, + + /** + * Method: addLayers + * Add a collection of layers to another collection of layers. Each layer in the list is tuple of + * { id, visible }. These layer collections represent the + * /ARCXML/REQUEST/get_image/PROPERTIES/LAYERLIST/LAYERDEF items in ArcXML + * + * TODO: Add support for dynamic layer rendering. + * + * Parameters: + * ll - {Array({id,visible})} A list of layer definitions. + * lyrs - {Array({id,visible})} A list of layer definitions. + */ + addLayers: function(ll, lyrs) { + for(var lind = 0, len=lyrs.length; lind < len; lind++) { + ll.push(lyrs[lind]); + } + }, + + /** + * Method: addImageSize + * Set the size of the requested image. + * + * Parameters: + * imsize - {Object} An ArcXML imagesize object. + * olsize - {} The image size to set. + */ + addImageSize: function(imsize, olsize) { + if (olsize !== null) { + imsize.width = olsize.w; + imsize.height = olsize.h; + imsize.printwidth = olsize.w; + imsize.printheight = olsize.h; + } + }, + + /** + * Method: addCoordSys + * Add the coordinate system information to an object. The object may be + * + * Parameters: + * featOrFilt - {Object} A featurecoordsys or filtercoordsys ArcXML structure. + * fsys - {String} or {} or {filtercoordsys} or + * {featurecoordsys} A projection representation. If it's a {String}, + * the value is assumed to be the SRID. If it's a {OpenLayers.Projection} + * AND Proj4js is available, the projection number and name are extracted + * from there. If it's a filter or feature ArcXML structure, it is copied. + */ + addCoordSys: function(featOrFilt, fsys) { + if (typeof fsys == "string") { + featOrFilt.id = parseInt(fsys); + featOrFilt.string = fsys; + } + // is this a proj4js instance? + else if (typeof fsys == "object" && fsys.proj !== null){ + featOrFilt.id = fsys.proj.srsProjNumber; + featOrFilt.string = fsys.proj.srsCode; + } else { + featOrFilt = fsys; + } + }, + + /** + * APIMethod: iserror + * Check to see if the response from the server was an error. + * + * Parameters: + * data - {String} or {DOMElement} data to read/parse. If nothing is supplied, + * the current response is examined. + * + * Returns: + * {Boolean} true if the response was an error. + */ + iserror: function(data) { + var ret = null; + + if (!data) { + ret = (this.response.error !== ''); + } else { + data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); + var errorNodes = data.documentElement.getElementsByTagName("ERROR"); + ret = (errorNodes !== null && errorNodes.length > 0); + } + + return ret; + }, + + /** + * APIMethod: read + * Read data from a string, and return an response. + * + * Parameters: + * data - {String} or {DOMElement} data to read/parse. + * + * Returns: + * {} An ArcXML response. Note that this response + * data may change in the future. + */ + read: function(data) { + if(typeof data == "string") { + data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); + } + + var arcNode = null; + if (data && data.documentElement) { + if(data.documentElement.nodeName == "ARCXML") { + arcNode = data.documentElement; + } else { + arcNode = data.documentElement.getElementsByTagName("ARCXML")[0]; + } + } + + // in Safari, arcNode will be there but will have a child named + // parsererror + if (!arcNode || arcNode.firstChild.nodeName === 'parsererror') { + var error, source; + try { + error = data.firstChild.nodeValue; + source = data.firstChild.childNodes[1].firstChild.nodeValue; + } catch (err) { + // pass + } + throw { + message: "Error parsing the ArcXML request", + error: error, + source: source + }; + } + + var response = this.parseResponse(arcNode); + return response; + }, + + /** + * APIMethod: write + * Generate an ArcXml document string for sending to an ArcIMS server. + * + * Returns: + * {String} A string representing the ArcXML document request. + */ + write: function(request) { + if (!request) { + request = this.request; + } + var root = this.createElementNS("", "ARCXML"); + root.setAttribute("version","1.1"); + + var reqElem = this.createElementNS("", "REQUEST"); + + if (request.get_image != null) { + var getElem = this.createElementNS("", "GET_IMAGE"); + reqElem.appendChild(getElem); + + var propElem = this.createElementNS("", "PROPERTIES"); + getElem.appendChild(propElem); + + var props = request.get_image.properties; + if (props.featurecoordsys != null) { + var feat = this.createElementNS("", "FEATURECOORDSYS"); + propElem.appendChild(feat); + + if (props.featurecoordsys.id === 0) { + feat.setAttribute("string", props.featurecoordsys['string']); + } + else { + feat.setAttribute("id", props.featurecoordsys.id); + } + } + + if (props.filtercoordsys != null) { + var filt = this.createElementNS("", "FILTERCOORDSYS"); + propElem.appendChild(filt); + + if (props.filtercoordsys.id === 0) { + filt.setAttribute("string", props.filtercoordsys.string); + } + else { + filt.setAttribute("id", props.filtercoordsys.id); + } + } + + if (props.envelope != null) { + var env = this.createElementNS("", "ENVELOPE"); + propElem.appendChild(env); + + env.setAttribute("minx", props.envelope.minx); + env.setAttribute("miny", props.envelope.miny); + env.setAttribute("maxx", props.envelope.maxx); + env.setAttribute("maxy", props.envelope.maxy); + } + + var imagesz = this.createElementNS("", "IMAGESIZE"); + propElem.appendChild(imagesz); + + imagesz.setAttribute("height", props.imagesize.height); + imagesz.setAttribute("width", props.imagesize.width); + + if (props.imagesize.height != props.imagesize.printheight || + props.imagesize.width != props.imagesize.printwidth) { + imagesz.setAttribute("printheight", props.imagesize.printheight); + imagesz.setArrtibute("printwidth", props.imagesize.printwidth); + } + + if (props.background != null) { + var backgrnd = this.createElementNS("", "BACKGROUND"); + propElem.appendChild(backgrnd); + + backgrnd.setAttribute("color", + props.background.color.r + "," + + props.background.color.g + "," + + props.background.color.b); + + if (props.background.transcolor !== null) { + backgrnd.setAttribute("transcolor", + props.background.transcolor.r + "," + + props.background.transcolor.g + "," + + props.background.transcolor.b); + } + } + + if (props.layerlist != null && props.layerlist.length > 0) { + var layerlst = this.createElementNS("", "LAYERLIST"); + propElem.appendChild(layerlst); + + for (var ld = 0; ld < props.layerlist.length; ld++) { + var ldef = this.createElementNS("", "LAYERDEF"); + layerlst.appendChild(ldef); + + ldef.setAttribute("id", props.layerlist[ld].id); + ldef.setAttribute("visible", props.layerlist[ld].visible); + + if (typeof props.layerlist[ld].query == "object") { + var query = props.layerlist[ld].query; + + if (query.where.length < 0) { + continue; + } + + var queryElem = null; + if (typeof query.spatialfilter == "boolean" && query.spatialfilter) { + // handle spatial filter madness + queryElem = this.createElementNS("", "SPATIALQUERY"); + } + else { + queryElem = this.createElementNS("", "QUERY"); + } + + queryElem.setAttribute("where", query.where); + + if (typeof query.accuracy == "number" && query.accuracy > 0) { + queryElem.setAttribute("accuracy", query.accuracy); + } + if (typeof query.featurelimit == "number" && query.featurelimit < 2000) { + queryElem.setAttribute("featurelimit", query.featurelimit); + } + if (typeof query.subfields == "string" && query.subfields != "#ALL#") { + queryElem.setAttribute("subfields", query.subfields); + } + if (typeof query.joinexpression == "string" && query.joinexpression.length > 0) { + queryElem.setAttribute("joinexpression", query.joinexpression); + } + if (typeof query.jointables == "string" && query.jointables.length > 0) { + queryElem.setAttribute("jointables", query.jointables); + } + + ldef.appendChild(queryElem); + } + + if (typeof props.layerlist[ld].renderer == "object") { + this.addRenderer(ldef, props.layerlist[ld].renderer); + } + } + } + } else if (request.get_feature != null) { + var getElem = this.createElementNS("", "GET_FEATURES"); + getElem.setAttribute("outputmode", "newxml"); + getElem.setAttribute("checkesc", "true"); + + if (request.get_feature.geometry) { + getElem.setAttribute("geometry", request.get_feature.geometry); + } + else { + getElem.setAttribute("geometry", "false"); + } + + if (request.get_feature.compact) { + getElem.setAttribute("compact", request.get_feature.compact); + } + + if (request.get_feature.featurelimit == "number") { + getElem.setAttribute("featurelimit", request.get_feature.featurelimit); + } + + getElem.setAttribute("globalenvelope", "true"); + reqElem.appendChild(getElem); + + if (request.get_feature.layer != null && request.get_feature.layer.length > 0) { + var lyrElem = this.createElementNS("", "LAYER"); + lyrElem.setAttribute("id", request.get_feature.layer); + getElem.appendChild(lyrElem); + } + + var fquery = request.get_feature.query; + if (fquery != null) { + var qElem = null; + if (fquery.isspatial) { + qElem = this.createElementNS("", "SPATIALQUERY"); + } else { + qElem = this.createElementNS("", "QUERY"); + } + getElem.appendChild(qElem); + + if (typeof fquery.accuracy == "number") { + qElem.setAttribute("accuracy", fquery.accuracy); + } + //qElem.setAttribute("featurelimit", "5"); + + if (fquery.featurecoordsys != null) { + var fcsElem1 = this.createElementNS("", "FEATURECOORDSYS"); + + if (fquery.featurecoordsys.id == 0) { + fcsElem1.setAttribute("string", fquery.featurecoordsys.string); + } else { + fcsElem1.setAttribute("id", fquery.featurecoordsys.id); + } + qElem.appendChild(fcsElem1); + } + + if (fquery.filtercoordsys != null) { + var fcsElem2 = this.createElementNS("", "FILTERCOORDSYS"); + + if (fquery.filtercoordsys.id === 0) { + fcsElem2.setAttribute("string", fquery.filtercoordsys.string); + } else { + fcsElem2.setAttribute("id", fquery.filtercoordsys.id); + } + qElem.appendChild(fcsElem2); + } + + if (fquery.buffer > 0) { + var bufElem = this.createElementNS("", "BUFFER"); + bufElem.setAttribute("distance", fquery.buffer); + qElem.appendChild(bufElem); + } + + if (fquery.isspatial) { + var spfElem = this.createElementNS("", "SPATIALFILTER"); + spfElem.setAttribute("relation", fquery.spatialfilter.relation); + qElem.appendChild(spfElem); + + if (fquery.spatialfilter.envelope) { + var envElem = this.createElementNS("", "ENVELOPE"); + envElem.setAttribute("minx", fquery.spatialfilter.envelope.minx); + envElem.setAttribute("miny", fquery.spatialfilter.envelope.miny); + envElem.setAttribute("maxx", fquery.spatialfilter.envelope.maxx); + envElem.setAttribute("maxy", fquery.spatialfilter.envelope.maxy); + spfElem.appendChild(envElem); + } else if(typeof fquery.spatialfilter.polygon == "object") { + spfElem.appendChild(this.writePolygonGeometry(fquery.spatialfilter.polygon)); + } + } + + if (fquery.where != null && fquery.where.length > 0) { + qElem.setAttribute("where", fquery.where); + } + } + } + + root.appendChild(reqElem); + + return OpenLayers.Format.XML.prototype.write.apply(this, [root]); + }, + + + addGroupRenderer: function(ldef, toprenderer) { + var topRelem = this.createElementNS("", "GROUPRENDERER"); + ldef.appendChild(topRelem); + + for (var rind = 0; rind < toprenderer.length; rind++) { + var renderer = toprenderer[rind]; + this.addRenderer(topRelem, renderer); + } + }, + + + addRenderer: function(topRelem, renderer) { + if (OpenLayers.Util.isArray(renderer)) { + this.addGroupRenderer(topRelem, renderer); + } else { + var renderElem = this.createElementNS("", renderer.type.toUpperCase() + "RENDERER"); + topRelem.appendChild(renderElem); + + if (renderElem.tagName == "VALUEMAPRENDERER") { + this.addValueMapRenderer(renderElem, renderer); + } else if (renderElem.tagName == "VALUEMAPLABELRENDERER") { + this.addValueMapLabelRenderer(renderElem, renderer); + } else if (renderElem.tagName == "SIMPLELABELRENDERER") { + this.addSimpleLabelRenderer(renderElem, renderer); + } else if (renderElem.tagName == "SCALEDEPENDENTRENDERER") { + this.addScaleDependentRenderer(renderElem, renderer); + } + } + }, + + + addScaleDependentRenderer: function(renderElem, renderer) { + if (typeof renderer.lower == "string" || typeof renderer.lower == "number") { + renderElem.setAttribute("lower", renderer.lower); + } + if (typeof renderer.upper == "string" || typeof renderer.upper == "number") { + renderElem.setAttribute("upper", renderer.upper); + } + + this.addRenderer(renderElem, renderer.renderer); + }, + + + addValueMapLabelRenderer: function(renderElem, renderer) { + renderElem.setAttribute("lookupfield", renderer.lookupfield); + renderElem.setAttribute("labelfield", renderer.labelfield); + + if (typeof renderer.exacts == "object") { + for (var ext=0, extlen=renderer.exacts.length; ext 0) { + response.error = this.getChildValue(errorNode, "Unknown error."); + } else { + var responseNode = data.getElementsByTagName("RESPONSE"); + + if (responseNode == null || responseNode.length == 0) { + response.error = "No RESPONSE tag found in ArcXML response."; + return response; + } + + var rtype = responseNode[0].firstChild.nodeName; + if (rtype == "#text") { + rtype = responseNode[0].firstChild.nextSibling.nodeName; + } + + if (rtype == "IMAGE") { + var envelopeNode = data.getElementsByTagName("ENVELOPE"); + var outputNode = data.getElementsByTagName("OUTPUT"); + + if (envelopeNode == null || envelopeNode.length == 0) { + response.error = "No ENVELOPE tag found in ArcXML response."; + } else if (outputNode == null || outputNode.length == 0) { + response.error = "No OUTPUT tag found in ArcXML response."; + } else { + var envAttr = this.parseAttributes(envelopeNode[0]); + var outputAttr = this.parseAttributes(outputNode[0]); + + if (typeof outputAttr.type == "string") { + response.image = { + envelope: envAttr, + output: { + type: outputAttr.type, + data: this.getChildValue(outputNode[0]) + } + }; + } else { + response.image = { envelope: envAttr, output: outputAttr }; + } + } + } else if (rtype == "FEATURES") { + var features = responseNode[0].getElementsByTagName("FEATURES"); + + // get the feature count + var featureCount = features[0].getElementsByTagName("FEATURECOUNT"); + response.features.featurecount = featureCount[0].getAttribute("count"); + + if (response.features.featurecount > 0) { + // get the feature envelope + var envelope = features[0].getElementsByTagName("ENVELOPE"); + response.features.envelope = this.parseAttributes(envelope[0], typeof(0)); + + // get the field values per feature + var featureList = features[0].getElementsByTagName("FEATURE"); + for (var fn = 0; fn < featureList.length; fn++) { + var feature = new OpenLayers.Feature.Vector(); + var fields = featureList[fn].getElementsByTagName("FIELD"); + + for (var fdn = 0; fdn < fields.length; fdn++) { + var fieldName = fields[fdn].getAttribute("name"); + var fieldValue = fields[fdn].getAttribute("value"); + feature.attributes[ fieldName ] = fieldValue; + } + + var geom = featureList[fn].getElementsByTagName("POLYGON"); + + if (geom.length > 0) { + // if there is a polygon, create an openlayers polygon, and assign + // it to the .geometry property of the feature + var ring = geom[0].getElementsByTagName("RING"); + + var polys = []; + for (var rn = 0; rn < ring.length; rn++) { + var linearRings = []; + linearRings.push(this.parsePointGeometry(ring[rn])); + + var holes = ring[rn].getElementsByTagName("HOLE"); + for (var hn = 0; hn < holes.length; hn++) { + linearRings.push(this.parsePointGeometry(holes[hn])); + } + holes = null; + polys.push(new OpenLayers.Geometry.Polygon(linearRings)); + linearRings = null; + } + ring = null; + + if (polys.length == 1) { + feature.geometry = polys[0]; + } else + { + feature.geometry = new OpenLayers.Geometry.MultiPolygon(polys); + } + } + + response.features.feature.push(feature); + } + } + } else { + response.error = "Unidentified response type."; + } + } + return response; + }, + + + /** + * Method: parseAttributes + * + * Parameters: + * node - {} An element to parse attributes from. + * + * Returns: + * {Object} An attributes object, with properties set to attribute values. + */ + parseAttributes: function(node,type) { + var attributes = {}; + for(var attr = 0; attr < node.attributes.length; attr++) { + if (type == "number") { + attributes[node.attributes[attr].nodeName] = parseFloat(node.attributes[attr].nodeValue); + } else { + attributes[node.attributes[attr].nodeName] = node.attributes[attr].nodeValue; + } + } + return attributes; + }, + + + /** + * Method: parsePointGeometry + * + * Parameters: + * node - {} An element to parse or arcxml data from. + * + * Returns: + * {} A linear ring represented by the node's points. + */ + parsePointGeometry: function(node) { + var ringPoints = []; + var coords = node.getElementsByTagName("COORDS"); + + if (coords.length > 0) { + // if coords is present, it's the only coords item + var coordArr = this.getChildValue(coords[0]); + coordArr = coordArr.split(/;/); + for (var cn = 0; cn < coordArr.length; cn++) { + var coordItems = coordArr[cn].split(/ /); + ringPoints.push(new OpenLayers.Geometry.Point(coordItems[0], coordItems[1])); + } + coords = null; + } else { + var point = node.getElementsByTagName("POINT"); + if (point.length > 0) { + for (var pn = 0; pn < point.length; pn++) { + ringPoints.push( + new OpenLayers.Geometry.Point( + parseFloat(point[pn].getAttribute("x")), + parseFloat(point[pn].getAttribute("y")) + ) + ); + } + } + point = null; + } + + return new OpenLayers.Geometry.LinearRing(ringPoints); + }, + + CLASS_NAME: "OpenLayers.Format.ArcXML" +}); + +OpenLayers.Format.ArcXML.Request = OpenLayers.Class({ + initialize: function(params) { + var defaults = { + get_image: { + properties: { + background: null, + /*{ + color: { r:255, g:255, b:255 }, + transcolor: null + },*/ + draw: true, + envelope: { + minx: 0, + miny: 0, + maxx: 0, + maxy: 0 + }, + featurecoordsys: { + id:0, + string:"", + datumtransformid:0, + datumtransformstring:"" + }, + filtercoordsys:{ + id:0, + string:"", + datumtransformid:0, + datumtransformstring:"" + }, + imagesize:{ + height:0, + width:0, + dpi:96, + printheight:0, + printwidth:0, + scalesymbols:false + }, + layerlist:[], + /* no support for legends */ + output:{ + baseurl:"", + legendbaseurl:"", + legendname:"", + legendpath:"", + legendurl:"", + name:"", + path:"", + type:"jpg", + url:"" + } + } + }, + + get_feature: { + layer: "", + query: { + isspatial: false, + featurecoordsys: { + id:0, + string:"", + datumtransformid:0, + datumtransformstring:"" + }, + filtercoordsys: { + id:0, + string:"", + datumtransformid:0, + datumtransformstring:"" + }, + buffer:0, + where:"", + spatialfilter: { + relation: "envelope_intersection", + envelope: null + } + } + }, + + environment: { + separators: { + cs:" ", + ts:";" + } + }, + + layer: [], + workspaces: [] + }; + + return OpenLayers.Util.extend(this, defaults); + }, + + CLASS_NAME: "OpenLayers.Format.ArcXML.Request" +}); + +OpenLayers.Format.ArcXML.Response = OpenLayers.Class({ + initialize: function(params) { + var defaults = { + image: { + envelope:null, + output:'' + }, + + features: { + featurecount: 0, + envelope: null, + feature: [] + }, + + error:'' + }; + + return OpenLayers.Util.extend(this, defaults); + }, + + CLASS_NAME: "OpenLayers.Format.ArcXML.Response" +}); +/* ====================================================================== + 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/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/Layer/ArcIMS.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 + * @requires OpenLayers/Format/ArcXML.js + * @requires OpenLayers/Request.js + */ + +/** + * Class: OpenLayers.Layer.ArcIMS + * Instances of OpenLayers.Layer.ArcIMS are used to display data from ESRI ArcIMS + * Mapping Services. Create a new ArcIMS layer with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Layer.ArcIMS = OpenLayers.Class(OpenLayers.Layer.Grid, { + + /** + * Constant: DEFAULT_PARAMS + * {Object} Default query string parameters. + */ + DEFAULT_PARAMS: { + ClientVersion: "9.2", + ServiceName: '' + }, + + /** + * APIProperty: featureCoordSys + * {String} Code for feature coordinate system. Default is "4326". + */ + featureCoordSys: "4326", + + /** + * APIProperty: filterCoordSys + * {String} Code for filter coordinate system. Default is "4326". + */ + filterCoordSys: "4326", + + /** + * APIProperty: layers + * {Array} An array of objects with layer properties. + */ + layers: null, + + /** + * APIProperty: async + * {Boolean} Request images asynchronously. Default is true. + */ + async: true, + + /** + * APIProperty: name + * {String} Layer name. Default is "ArcIMS". + */ + name: "ArcIMS", + + /** + * APIProperty: isBaseLayer + * {Boolean} The layer is a base layer. Default is true. + */ + isBaseLayer: true, + + /** + * Constant: DEFAULT_OPTIONS + * {Object} Default layers properties. + */ + DEFAULT_OPTIONS: { + tileSize: new OpenLayers.Size(512, 512), + featureCoordSys: "4326", + filterCoordSys: "4326", + layers: null, + isBaseLayer: true, + async: true, + name: "ArcIMS" + }, + + /** + * Constructor: OpenLayers.Layer.ArcIMS + * Create a new ArcIMS layer object. + * + * Example: + * (code) + * var arcims = new OpenLayers.Layer.ArcIMS( + * "Global Sample", + * "http://sample.avencia.com/servlet/com.esri.esrimap.Esrimap", + * { + * service: "OpenLayers_Sample", + * layers: [ + * // layers to manipulate + * {id: "1", visible: true} + * ] + * } + * ); + * (end) + * + * Parameters: + * name - {String} A name for the layer + * url - {String} Base url for the ArcIMS server + * options - {Object} Optional object with properties to be set on the + * layer. + */ + initialize: function(name, url, options) { + + this.tileSize = new OpenLayers.Size(512, 512); + + // parameters + this.params = OpenLayers.Util.applyDefaults( + {ServiceName: options.serviceName}, + this.DEFAULT_PARAMS + ); + this.options = OpenLayers.Util.applyDefaults( + options, this.DEFAULT_OPTIONS + ); + + OpenLayers.Layer.Grid.prototype.initialize.apply( + this, [name, url, this.params, options] + ); + + //layer is transparent + if (this.transparent) { + + // unless explicitly set in options, make layer an overlay + if (!this.isBaseLayer) { + this.isBaseLayer = false; + } + + // jpegs can never be transparent, so intelligently switch the + // format, depending on the browser's capabilities + if (this.format == "image/jpeg") { + this.format = OpenLayers.Util.alphaHack() ? "image/gif" : "image/png"; + } + } + + // create an empty layer list if no layers specified in the options + if (this.options.layers === null) { + this.options.layers = []; + } + }, + + /** + * Method: getURL + * Return an image url this layer. + * + * Parameters: + * bounds - {} A bounds representing the bbox for the + * request. + * + * Returns: + * {String} A string with the map image's url. + */ + getURL: function(bounds) { + var url = ""; + bounds = this.adjustBounds(bounds); + + // create an arcxml request to generate the image + var axlReq = new OpenLayers.Format.ArcXML( + OpenLayers.Util.extend(this.options, { + requesttype: "image", + envelope: bounds.toArray(), + tileSize: this.tileSize + }) + ); + + // create a synchronous ajax request to get an arcims image + var req = new OpenLayers.Request.POST({ + url: this.getFullRequestString(), + data: axlReq.write(), + async: false + }); + + // if the response exists + if (req != null) { + var doc = req.responseXML; + + if (!doc || !doc.documentElement) { + doc = req.responseText; + } + + // create a new arcxml format to read the response + var axlResp = new OpenLayers.Format.ArcXML(); + var arcxml = axlResp.read(doc); + url = this.getUrlOrImage(arcxml.image.output); + } + + return url; + }, + + + /** + * Method: getURLasync + * Get an image url this layer asynchronously, and execute a callback + * when the image url is generated. + * + * Parameters: + * bounds - {} A bounds representing the bbox for the + * request. + * callback - {Function} Function to call when image url is retrieved. + * scope - {Object} The scope of the callback method. + */ + getURLasync: function(bounds, callback, scope) { + bounds = this.adjustBounds(bounds); + + // create an arcxml request to generate the image + var axlReq = new OpenLayers.Format.ArcXML( + OpenLayers.Util.extend(this.options, { + requesttype: "image", + envelope: bounds.toArray(), + tileSize: this.tileSize + }) + ); + + // create an asynchronous ajax request to get an arcims image + OpenLayers.Request.POST({ + url: this.getFullRequestString(), + async: true, + data: axlReq.write(), + callback: function(req) { + // process the response from ArcIMS, and call the callback function + // to set the image URL + var doc = req.responseXML; + if (!doc || !doc.documentElement) { + doc = req.responseText; + } + + // create a new arcxml format to read the response + var axlResp = new OpenLayers.Format.ArcXML(); + var arcxml = axlResp.read(doc); + + callback.call(scope, this.getUrlOrImage(arcxml.image.output)); + }, + scope: this + }); + }, + + /** + * Method: getUrlOrImage + * Extract a url or image from the ArcXML image output. + * + * Parameters: + * output - {Object} The image.output property of the object returned from + * the ArcXML format read method. + * + * Returns: + * {String} A URL for an image (potentially with the data protocol). + */ + getUrlOrImage: function(output) { + var ret = ""; + if(output.url) { + // If the image response output url is a string, then the image + // data is not inline. + ret = output.url; + } else if(output.data) { + // The image data is inline and base64 encoded, create a data + // url for the image. This will only work for small images, + // due to browser url length limits. + ret = "data:image/" + output.type + + ";base64," + output.data; + } + return ret; + }, + + /** + * Method: setLayerQuery + * Set the query definition on this layer. Query definitions are used to + * render parts of the spatial data in an image, and can be used to + * filter features or layers in the ArcIMS service. + * + * Parameters: + * id - {String} The ArcIMS layer ID. + * querydef - {Object} The query definition to apply to this layer. + */ + setLayerQuery: function(id, querydef) { + // find the matching layer, if it exists + for (var lyr = 0; lyr < this.options.layers.length; lyr++) { + if (id == this.options.layers[lyr].id) { + // replace this layer definition + this.options.layers[lyr].query = querydef; + return; + } + } + + // no layer found, create a new definition + this.options.layers.push({id: id, visible: true, query: querydef}); + }, + + /** + * Method: getFeatureInfo + * Get feature information from ArcIMS. Using the applied geometry, apply + * the options to the query (buffer, area/envelope intersection), and + * query the ArcIMS service. + * + * A note about accuracy: + * ArcIMS interprets the accuracy attribute in feature requests to be + * something like the 'modulus' operator on feature coordinates, + * applied to the database geometry of the feature. It doesn't round, + * so your feature coordinates may be up to (1 x accuracy) offset from + * the actual feature coordinates. If the accuracy of the layer is not + * specified, the accuracy will be computed to be approximately 1 + * feature coordinate per screen pixel. + * + * Parameters: + * geometry - {} or {} The + * geometry to use when making the query. This should be a closed + * polygon for behavior approximating a free selection. + * layer - {Object} The ArcIMS layer definition. This is an anonymous object + * that looks like: + * (code) + * { + * id: "ArcXML layer ID", // the ArcXML layer ID + * query: { + * where: "STATE = 'PA'", // the where clause of the query + * accuracy: 100 // the accuracy of the returned feature + * } + * } + * (end) + * options - {Object} Object with non-default properties to set on the layer. + * Supported properties are buffer, callback, scope, and any other + * properties applicable to the ArcXML format. Set the 'callback' and + * 'scope' for an object and function to recieve the parsed features + * from ArcIMS. + */ + getFeatureInfo: function(geometry, layer, options) { + // set the buffer to 1 unit (dd/m/ft?) by default + var buffer = options.buffer || 1; + // empty callback by default + var callback = options.callback || function() {}; + // default scope is window (global) + var scope = options.scope || window; + + // apply these option to the request options + var requestOptions = {}; + OpenLayers.Util.extend(requestOptions, this.options); + + // this is a feature request + requestOptions.requesttype = "feature"; + + if (geometry instanceof OpenLayers.LonLat) { + // create an envelope if the geometry is really a lon/lat + requestOptions.polygon = null; + requestOptions.envelope = [ + geometry.lon - buffer, + geometry.lat - buffer, + geometry.lon + buffer, + geometry.lat + buffer + ]; + } else if (geometry instanceof OpenLayers.Geometry.Polygon) { + // use the polygon assigned, and empty the envelope + requestOptions.envelope = null; + requestOptions.polygon = geometry; + } + + // create an arcxml request to get feature requests + var arcxml = new OpenLayers.Format.ArcXML(requestOptions); + + // apply any get feature options to the arcxml request + OpenLayers.Util.extend(arcxml.request.get_feature, options); + + arcxml.request.get_feature.layer = layer.id; + if (typeof layer.query.accuracy == "number") { + // set the accuracy if it was specified + arcxml.request.get_feature.query.accuracy = layer.query.accuracy; + } else { + // guess that the accuracy is 1 per screen pixel + var mapCenter = this.map.getCenter(); + var viewPx = this.map.getViewPortPxFromLonLat(mapCenter); + viewPx.x++; + var mapOffCenter = this.map.getLonLatFromPixel(viewPx); + arcxml.request.get_feature.query.accuracy = mapOffCenter.lon - mapCenter.lon; + } + + // set the get_feature query to be the same as the layer passed in + arcxml.request.get_feature.query.where = layer.query.where; + + // use area_intersection + arcxml.request.get_feature.query.spatialfilter.relation = "area_intersection"; + + // create a new asynchronous request to get the feature info + OpenLayers.Request.POST({ + url: this.getFullRequestString({'CustomService': 'Query'}), + data: arcxml.write(), + callback: function(request) { + // parse the arcxml response + var response = arcxml.parseResponse(request.responseText); + + if (!arcxml.iserror()) { + // if the arcxml is not an error, call the callback with the features parsed + callback.call(scope, response.features); + } else { + // if the arcxml is an error, return null features selected + callback.call(scope, null); + } + } + }); + }, + + /** + * Method: clone + * Create a clone of this layer + * + * Returns: + * {} An exact clone of this layer + */ + clone: function (obj) { + + if (obj == null) { + obj = new OpenLayers.Layer.ArcIMS(this.name, + this.url, + this.getOptions()); + } + + //get all additions from superclasses + obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]); + + // copy/set any non-init, non-simple values here + + return obj; + }, + + CLASS_NAME: "OpenLayers.Layer.ArcIMS" +}); +/* ====================================================================== + OpenLayers/Control/PanZoom.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.PanZoom + * The PanZoom is a visible control, composed of a + * and a . By + * default it is drawn in the upper left corner of the map. + * + * Inherits from: + * - + */ +OpenLayers.Control.PanZoom = OpenLayers.Class(OpenLayers.Control, { + + /** + * APIProperty: slideFactor + * {Integer} Number of pixels by which we'll pan the map in any direction + * on clicking the arrow buttons. If you want to pan by some ratio + * of the map dimensions, use instead. + */ + slideFactor: 50, + + /** + * APIProperty: slideRatio + * {Number} The fraction of map width/height by which we'll pan the map + * on clicking the arrow buttons. Default is null. If set, will + * override . E.g. if slideRatio is .5, then the Pan Up + * button will pan up half the map height. + */ + slideRatio: null, + + /** + * Property: buttons + * {Array(DOMElement)} Array of Button Divs + */ + buttons: null, + + /** + * Property: position + * {} + */ + position: null, + + /** + * Constructor: OpenLayers.Control.PanZoom + * + * Parameters: + * options - {Object} + */ + initialize: function(options) { + this.position = new OpenLayers.Pixel(OpenLayers.Control.PanZoom.X, + OpenLayers.Control.PanZoom.Y); + OpenLayers.Control.prototype.initialize.apply(this, arguments); + }, + + /** + * APIMethod: destroy + */ + destroy: function() { + if (this.map) { + this.map.events.unregister("buttonclick", this, this.onButtonClick); + } + this.removeButtons(); + this.buttons = null; + this.position = null; + OpenLayers.Control.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: setMap + * + * Properties: + * map - {} + */ + setMap: function(map) { + OpenLayers.Control.prototype.setMap.apply(this, arguments); + this.map.events.register("buttonclick", this, this.onButtonClick); + }, + + /** + * Method: draw + * + * Parameters: + * px - {} + * + * Returns: + * {DOMElement} A reference to the container div for the PanZoom control. + */ + draw: function(px) { + // initialize our internal div + OpenLayers.Control.prototype.draw.apply(this, arguments); + px = this.position; + + // place the controls + this.buttons = []; + + var sz = {w: 18, h: 18}; + var centered = new OpenLayers.Pixel(px.x+sz.w/2, px.y); + + this._addButton("panup", "north-mini.png", centered, sz); + px.y = centered.y+sz.h; + this._addButton("panleft", "west-mini.png", px, sz); + this._addButton("panright", "east-mini.png", px.add(sz.w, 0), sz); + this._addButton("pandown", "south-mini.png", + centered.add(0, sz.h*2), sz); + this._addButton("zoomin", "zoom-plus-mini.png", + centered.add(0, sz.h*3+5), sz); + this._addButton("zoomworld", "zoom-world-mini.png", + centered.add(0, sz.h*4+5), sz); + this._addButton("zoomout", "zoom-minus-mini.png", + centered.add(0, sz.h*5+5), sz); + return this.div; + }, + + /** + * Method: _addButton + * + * Parameters: + * id - {String} + * img - {String} + * xy - {} + * sz - {} + * + * Returns: + * {DOMElement} A Div (an alphaImageDiv, to be precise) that contains the + * image of the button, and has all the proper event handlers set. + */ + _addButton:function(id, img, xy, sz) { + var imgLocation = OpenLayers.Util.getImageLocation(img); + var btn = OpenLayers.Util.createAlphaImageDiv( + this.id + "_" + id, + xy, sz, imgLocation, "absolute"); + btn.style.cursor = "pointer"; + //we want to add the outer div + this.div.appendChild(btn); + btn.action = id; + btn.className = "olButton"; + + //we want to remember/reference the outer div + this.buttons.push(btn); + return btn; + }, + + /** + * Method: _removeButton + * + * Parameters: + * btn - {Object} + */ + _removeButton: function(btn) { + this.div.removeChild(btn); + OpenLayers.Util.removeItem(this.buttons, btn); + }, + + /** + * Method: removeButtons + */ + removeButtons: function() { + for(var i=this.buttons.length-1; i>=0; --i) { + this._removeButton(this.buttons[i]); + } + }, + + /** + * Method: onButtonClick + * + * Parameters: + * evt - {Event} + */ + onButtonClick: function(evt) { + var btn = evt.buttonElement; + switch (btn.action) { + case "panup": + this.map.pan(0, -this.getSlideFactor("h")); + break; + case "pandown": + this.map.pan(0, this.getSlideFactor("h")); + break; + case "panleft": + this.map.pan(-this.getSlideFactor("w"), 0); + break; + case "panright": + this.map.pan(this.getSlideFactor("w"), 0); + break; + case "zoomin": + this.map.zoomIn(); + break; + case "zoomout": + this.map.zoomOut(); + break; + case "zoomworld": + this.map.zoomToMaxExtent(); + break; + } + }, + + /** + * Method: getSlideFactor + * + * Parameters: + * dim - {String} "w" or "h" (for width or height). + * + * Returns: + * {Number} The slide factor for panning in the requested direction. + */ + getSlideFactor: function(dim) { + return this.slideRatio ? + this.map.getSize()[dim] * this.slideRatio : + this.slideFactor; + }, + + CLASS_NAME: "OpenLayers.Control.PanZoom" +}); + +/** + * Constant: X + * {Integer} + */ +OpenLayers.Control.PanZoom.X = 4; + +/** + * Constant: Y + * {Integer} + */ +OpenLayers.Control.PanZoom.Y = 4; +/* ====================================================================== + OpenLayers/Control/PanZoomBar.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/PanZoom.js + */ + +/** + * Class: OpenLayers.Control.PanZoomBar + * The PanZoomBar is a visible control composed of a + * and a . + * By default it is displayed in the upper left corner of the map as 4 + * directional arrows above a vertical slider. + * + * Inherits from: + * - + */ +OpenLayers.Control.PanZoomBar = OpenLayers.Class(OpenLayers.Control.PanZoom, { + + /** + * APIProperty: zoomStopWidth + */ + zoomStopWidth: 18, + + /** + * APIProperty: zoomStopHeight + */ + zoomStopHeight: 11, + + /** + * Property: slider + */ + slider: null, + + /** + * Property: sliderEvents + * {} + */ + sliderEvents: null, + + /** + * Property: zoombarDiv + * {DOMElement} + */ + zoombarDiv: null, + + /** + * APIProperty: zoomWorldIcon + * {Boolean} + */ + zoomWorldIcon: false, + + /** + * APIProperty: panIcons + * {Boolean} Set this property to false not to display the pan icons. If + * false the zoom world icon is placed under the zoom bar. Defaults to + * true. + */ + panIcons: true, + + /** + * APIProperty: forceFixedZoomLevel + * {Boolean} Force a fixed zoom level even though the map has + * fractionalZoom + */ + forceFixedZoomLevel: false, + + /** + * Property: mouseDragStart + * {} + */ + mouseDragStart: null, + + /** + * Property: deltaY + * {Number} The cumulative vertical pixel offset during a zoom bar drag. + */ + deltaY: null, + + /** + * Property: zoomStart + * {} + */ + zoomStart: null, + + /** + * Constructor: OpenLayers.Control.PanZoomBar + */ + + /** + * APIMethod: destroy + */ + destroy: function() { + + this._removeZoomBar(); + + this.map.events.un({ + "changebaselayer": this.redraw, + "updatesize": this.redraw, + scope: this + }); + + OpenLayers.Control.PanZoom.prototype.destroy.apply(this, arguments); + + delete this.mouseDragStart; + delete this.zoomStart; + }, + + /** + * Method: setMap + * + * Parameters: + * map - {} + */ + setMap: function(map) { + OpenLayers.Control.PanZoom.prototype.setMap.apply(this, arguments); + this.map.events.on({ + "changebaselayer": this.redraw, + "updatesize": this.redraw, + scope: this + }); + }, + + /** + * Method: redraw + * clear the div and start over. + */ + redraw: function() { + if (this.div != null) { + this.removeButtons(); + this._removeZoomBar(); + } + this.draw(); + }, + + /** + * Method: draw + * + * Parameters: + * px - {} + */ + draw: function(px) { + // initialize our internal div + OpenLayers.Control.prototype.draw.apply(this, arguments); + px = this.position.clone(); + + // place the controls + this.buttons = []; + + var sz = {w: 18, h: 18}; + if (this.panIcons) { + var centered = new OpenLayers.Pixel(px.x+sz.w/2, px.y); + var wposition = sz.w; + + if (this.zoomWorldIcon) { + centered = new OpenLayers.Pixel(px.x+sz.w, px.y); + } + + this._addButton("panup", "north-mini.png", centered, sz); + px.y = centered.y+sz.h; + this._addButton("panleft", "west-mini.png", px, sz); + if (this.zoomWorldIcon) { + this._addButton("zoomworld", "zoom-world-mini.png", px.add(sz.w, 0), sz); + + wposition *= 2; + } + this._addButton("panright", "east-mini.png", px.add(wposition, 0), sz); + this._addButton("pandown", "south-mini.png", centered.add(0, sz.h*2), sz); + this._addButton("zoomin", "zoom-plus-mini.png", centered.add(0, sz.h*3+5), sz); + centered = this._addZoomBar(centered.add(0, sz.h*4 + 5)); + this._addButton("zoomout", "zoom-minus-mini.png", centered, sz); + } + else { + this._addButton("zoomin", "zoom-plus-mini.png", px, sz); + centered = this._addZoomBar(px.add(0, sz.h)); + this._addButton("zoomout", "zoom-minus-mini.png", centered, sz); + if (this.zoomWorldIcon) { + centered = centered.add(0, sz.h+3); + this._addButton("zoomworld", "zoom-world-mini.png", centered, sz); + } + } + return this.div; + }, + + /** + * Method: _addZoomBar + * + * Parameters: + * centered - {} where zoombar drawing is to start. + */ + _addZoomBar:function(centered) { + var imgLocation = OpenLayers.Util.getImageLocation("slider.png"); + var id = this.id + "_" + this.map.id; + var minZoom = this.map.getMinZoom(); + var zoomsToEnd = this.map.getNumZoomLevels() - 1 - this.map.getZoom(); + var slider = OpenLayers.Util.createAlphaImageDiv(id, + centered.add(-1, zoomsToEnd * this.zoomStopHeight), + {w: 20, h: 9}, + imgLocation, + "absolute"); + slider.style.cursor = "move"; + this.slider = slider; + + this.sliderEvents = new OpenLayers.Events(this, slider, null, true, + {includeXY: true}); + this.sliderEvents.on({ + "touchstart": this.zoomBarDown, + "touchmove": this.zoomBarDrag, + "touchend": this.zoomBarUp, + "mousedown": this.zoomBarDown, + "mousemove": this.zoomBarDrag, + "mouseup": this.zoomBarUp + }); + + var sz = { + w: this.zoomStopWidth, + h: this.zoomStopHeight * (this.map.getNumZoomLevels() - minZoom) + }; + var imgLocation = OpenLayers.Util.getImageLocation("zoombar.png"); + var div = null; + + if (OpenLayers.Util.alphaHack()) { + var id = this.id + "_" + this.map.id; + div = OpenLayers.Util.createAlphaImageDiv(id, centered, + {w: sz.w, h: this.zoomStopHeight}, + imgLocation, + "absolute", null, "crop"); + div.style.height = sz.h + "px"; + } else { + div = OpenLayers.Util.createDiv( + 'OpenLayers_Control_PanZoomBar_Zoombar' + this.map.id, + centered, + sz, + imgLocation); + } + div.style.cursor = "pointer"; + div.className = "olButton"; + this.zoombarDiv = div; + + this.div.appendChild(div); + + this.startTop = parseInt(div.style.top); + this.div.appendChild(slider); + + this.map.events.register("zoomend", this, this.moveZoomBar); + + centered = centered.add(0, + this.zoomStopHeight * (this.map.getNumZoomLevels() - minZoom)); + return centered; + }, + + /** + * Method: _removeZoomBar + */ + _removeZoomBar: function() { + this.sliderEvents.un({ + "touchstart": this.zoomBarDown, + "touchmove": this.zoomBarDrag, + "touchend": this.zoomBarUp, + "mousedown": this.zoomBarDown, + "mousemove": this.zoomBarDrag, + "mouseup": this.zoomBarUp + }); + this.sliderEvents.destroy(); + + this.div.removeChild(this.zoombarDiv); + this.zoombarDiv = null; + this.div.removeChild(this.slider); + this.slider = null; + + this.map.events.unregister("zoomend", this, this.moveZoomBar); + }, + + /** + * Method: onButtonClick + * + * Parameters: + * evt - {Event} + */ + onButtonClick: function(evt) { + OpenLayers.Control.PanZoom.prototype.onButtonClick.apply(this, arguments); + if (evt.buttonElement === this.zoombarDiv) { + var levels = evt.buttonXY.y / this.zoomStopHeight; + if(this.forceFixedZoomLevel || !this.map.fractionalZoom) { + levels = Math.floor(levels); + } + var zoom = (this.map.getNumZoomLevels() - 1) - levels; + zoom = Math.min(Math.max(zoom, 0), this.map.getNumZoomLevels() - 1); + this.map.zoomTo(zoom); + } + }, + + /** + * Method: passEventToSlider + * This function is used to pass events that happen on the div, or the map, + * through to the slider, which then does its moving thing. + * + * Parameters: + * evt - {} + */ + passEventToSlider:function(evt) { + this.sliderEvents.handleBrowserEvent(evt); + }, + + /* + * Method: zoomBarDown + * event listener for clicks on the slider + * + * Parameters: + * evt - {} + */ + zoomBarDown:function(evt) { + if (!OpenLayers.Event.isLeftClick(evt) && !OpenLayers.Event.isSingleTouch(evt)) { + return; + } + this.map.events.on({ + "touchmove": this.passEventToSlider, + "mousemove": this.passEventToSlider, + "mouseup": this.passEventToSlider, + scope: this + }); + this.mouseDragStart = evt.xy.clone(); + this.zoomStart = evt.xy.clone(); + this.div.style.cursor = "move"; + // reset the div offsets just in case the div moved + this.zoombarDiv.offsets = null; + OpenLayers.Event.stop(evt); + }, + + /* + * Method: zoomBarDrag + * This is what happens when a click has occurred, and the client is + * dragging. Here we must ensure that the slider doesn't go beyond the + * bottom/top of the zoombar div, as well as moving the slider to its new + * visual location + * + * Parameters: + * evt - {} + */ + zoomBarDrag:function(evt) { + if (this.mouseDragStart != null) { + var deltaY = this.mouseDragStart.y - evt.xy.y; + var offsets = OpenLayers.Util.pagePosition(this.zoombarDiv); + if ((evt.clientY - offsets[1]) > 0 && + (evt.clientY - offsets[1]) < parseInt(this.zoombarDiv.style.height) - 2) { + var newTop = parseInt(this.slider.style.top) - deltaY; + this.slider.style.top = newTop+"px"; + this.mouseDragStart = evt.xy.clone(); + } + // set cumulative displacement + this.deltaY = this.zoomStart.y - evt.xy.y; + OpenLayers.Event.stop(evt); + } + }, + + /* + * Method: zoomBarUp + * Perform cleanup when a mouseup event is received -- discover new zoom + * level and switch to it. + * + * Parameters: + * evt - {} + */ + zoomBarUp:function(evt) { + if (!OpenLayers.Event.isLeftClick(evt) && evt.type !== "touchend") { + return; + } + if (this.mouseDragStart) { + this.div.style.cursor=""; + this.map.events.un({ + "touchmove": this.passEventToSlider, + "mouseup": this.passEventToSlider, + "mousemove": this.passEventToSlider, + scope: this + }); + var zoomLevel = this.map.zoom; + if (!this.forceFixedZoomLevel && this.map.fractionalZoom) { + zoomLevel += this.deltaY/this.zoomStopHeight; + zoomLevel = Math.min(Math.max(zoomLevel, 0), + this.map.getNumZoomLevels() - 1); + } else { + zoomLevel += this.deltaY/this.zoomStopHeight; + zoomLevel = Math.max(Math.round(zoomLevel), 0); + } + this.map.zoomTo(zoomLevel); + this.mouseDragStart = null; + this.zoomStart = null; + this.deltaY = 0; + OpenLayers.Event.stop(evt); + } + }, + + /* + * Method: moveZoomBar + * Change the location of the slider to match the current zoom level. + */ + moveZoomBar:function() { + var newTop = + ((this.map.getNumZoomLevels()-1) - this.map.getZoom()) * + this.zoomStopHeight + this.startTop + 1; + this.slider.style.top = newTop + "px"; + }, + + CLASS_NAME: "OpenLayers.Control.PanZoomBar" +}); +/* ====================================================================== + OpenLayers/Format/WFSCapabilities.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 + */ + +/** + * Class: OpenLayers.Format.WFSCapabilities + * Read WFS Capabilities. + * + * Inherits from: + * - + */ +OpenLayers.Format.WFSCapabilities = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, { + + /** + * APIProperty: defaultVersion + * {String} Version number to assume if none found. Default is "1.1.0". + */ + defaultVersion: "1.1.0", + + /** + * Constructor: OpenLayers.Format.WFSCapabilities + * Create a new parser for WFS capabilities. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + + /** + * APIMethod: read + * Read capabilities data from a string, and return a list of layers. + * + * Parameters: + * data - {String} or {DOMElement} data to read/parse. + * + * Returns: + * {Array} List of named layers. + */ + + CLASS_NAME: "OpenLayers.Format.WFSCapabilities" + +}); +/* ====================================================================== + OpenLayers/Format/WFSCapabilities/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/WFSCapabilities.js + */ + +/** + * Class: OpenLayers.Format.WFSCapabilities.v1 + * Abstract class not to be instantiated directly. + * + * Inherits from: + * - + */ +OpenLayers.Format.WFSCapabilities.v1 = OpenLayers.Class( + OpenLayers.Format.XML, { + + /** + * Property: namespaces + * {Object} Mapping of namespace aliases to namespace URIs. + */ + namespaces: { + wfs: "http://www.opengis.net/wfs", + xlink: "http://www.w3.org/1999/xlink", + xsi: "http://www.w3.org/2001/XMLSchema-instance", + ows: "http://www.opengis.net/ows" + }, + + + /** + * APIProperty: errorProperty + * {String} Which property of the returned object to check for in order to + * determine whether or not parsing has failed. In the case that the + * errorProperty is undefined on the returned object, the document will be + * run through an OGCExceptionReport parser. + */ + errorProperty: "featureTypeList", + + /** + * Property: defaultPrefix + */ + defaultPrefix: "wfs", + + /** + * Constructor: OpenLayers.Format.WFSCapabilities.v1_1 + * Create an instance of one of the subclasses. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + + /** + * APIMethod: read + * Read capabilities data from a string, and return a list of layers. + * + * Parameters: + * data - {String} or {DOMElement} data to read/parse. + * + * Returns: + * {Array} List of named layers. + */ + read: function(data) { + if(typeof data == "string") { + data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); + } + var raw = data; + if(data && data.nodeType == 9) { + data = data.documentElement; + } + var capabilities = {}; + this.readNode(data, capabilities); + return capabilities; + }, + + /** + * 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": { + "WFS_Capabilities": function(node, obj) { + this.readChildNodes(node, obj); + }, + "FeatureTypeList": function(node, request) { + request.featureTypeList = { + featureTypes: [] + }; + this.readChildNodes(node, request.featureTypeList); + }, + "FeatureType": function(node, featureTypeList) { + var featureType = {}; + this.readChildNodes(node, featureType); + featureTypeList.featureTypes.push(featureType); + }, + "Name": function(node, obj) { + var name = this.getChildValue(node); + if(name) { + var parts = name.split(":"); + obj.name = parts.pop(); + if(parts.length > 0) { + obj.featureNS = this.lookupNamespaceURI(node, parts[0]); + } + } + }, + "Title": function(node, obj) { + var title = this.getChildValue(node); + if(title) { + obj.title = title; + } + }, + "Abstract": function(node, obj) { + var abst = this.getChildValue(node); + if(abst) { + obj["abstract"] = abst; + } + } + } + }, + + CLASS_NAME: "OpenLayers.Format.WFSCapabilities.v1" + +}); +/* ====================================================================== + OpenLayers/Format/WFSCapabilities/v1_1_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/WFSCapabilities/v1.js + * @requires OpenLayers/Format/OWSCommon/v1.js + */ + +/** + * Class: OpenLayers.Format.WFSCapabilities/v1_1_0 + * Read WFS Capabilities version 1.1.0. + * + * Inherits from: + * - + */ +OpenLayers.Format.WFSCapabilities.v1_1_0 = OpenLayers.Class( + OpenLayers.Format.WFSCapabilities.v1, { + + /** + * Property: regExes + * Compiled regular expressions for manipulating strings. + */ + regExes: { + trimSpace: (/^\s*|\s*$/g), + removeSpace: (/\s*/g), + splitSpace: (/\s+/), + trimComma: (/\s*,\s*/g) + }, + + /** + * Constructor: OpenLayers.Format.WFSCapabilities.v1_1_0 + * Create a new parser for WFS capabilities version 1.1.0. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + + /** + * 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({ + "DefaultSRS": function(node, obj) { + var defaultSRS = this.getChildValue(node); + if (defaultSRS) { + obj.srs = defaultSRS; + } + } + }, OpenLayers.Format.WFSCapabilities.v1.prototype.readers["wfs"]), + "ows": OpenLayers.Format.OWSCommon.v1.prototype.readers.ows + }, + + CLASS_NAME: "OpenLayers.Format.WFSCapabilities.v1_1_0" + +}); +/* ====================================================================== + OpenLayers/Layer/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/Layer.js + * @requires OpenLayers/Tile/Image.js + */ + +/** + * Class: OpenLayers.Layer.Image + * Instances of OpenLayers.Layer.Image are used to display data from a web + * accessible image as a map layer. Create a new image layer with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Layer.Image = OpenLayers.Class(OpenLayers.Layer, { + + /** + * Property: isBaseLayer + * {Boolean} The layer is a base layer. Default is true. Set this property + * in the layer options + */ + isBaseLayer: true, + + /** + * Property: url + * {String} URL of the image to use + */ + url: null, + + /** + * Property: extent + * {} The image bounds in map units. This extent will + * also be used as the default maxExtent for the layer. If you wish + * to have a maxExtent that is different than the image extent, set the + * maxExtent property of the options argument (as with any other layer). + */ + extent: null, + + /** + * Property: size + * {} The image size in pixels + */ + size: null, + + /** + * Property: tile + * {} + */ + tile: null, + + /** + * Property: aspectRatio + * {Float} The ratio of height/width represented by a single pixel in the + * graphic + */ + aspectRatio: null, + + /** + * Constructor: OpenLayers.Layer.Image + * Create a new image layer + * + * Parameters: + * name - {String} A name for the layer. + * url - {String} Relative or absolute path to the image + * extent - {} The extent represented by the image + * size - {} The size (in pixels) of the image + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function(name, url, extent, size, options) { + this.url = url; + this.extent = extent; + this.maxExtent = extent; + this.size = size; + OpenLayers.Layer.prototype.initialize.apply(this, [name, options]); + + this.aspectRatio = (this.extent.getHeight() / this.size.h) / + (this.extent.getWidth() / this.size.w); + }, + + /** + * Method: destroy + * Destroy this layer + */ + destroy: function() { + if (this.tile) { + this.removeTileMonitoringHooks(this.tile); + this.tile.destroy(); + this.tile = null; + } + OpenLayers.Layer.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: clone + * Create a clone of this layer + * + * Paramters: + * obj - {Object} An optional layer (is this ever used?) + * + * Returns: + * {} An exact copy of this layer + */ + clone: function(obj) { + + if(obj == null) { + obj = new OpenLayers.Layer.Image(this.name, + this.url, + this.extent, + this.size, + 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: setMap + * + * Parameters: + * map - {} + */ + setMap: function(map) { + /** + * If nothing to do with resolutions has been set, assume a single + * resolution determined by ratio*extent/size - if an image has a + * pixel aspect ratio different than one (as calculated above), the + * image will be stretched in one dimension only. + */ + if( this.options.maxResolution == null ) { + this.options.maxResolution = this.aspectRatio * + this.extent.getWidth() / + this.size.w; + } + OpenLayers.Layer.prototype.setMap.apply(this, arguments); + }, + + /** + * Method: moveTo + * Create the tile for the image or resize it for the new resolution + * + * Parameters: + * bounds - {} + * zoomChanged - {Boolean} + * dragging - {Boolean} + */ + moveTo:function(bounds, zoomChanged, dragging) { + OpenLayers.Layer.prototype.moveTo.apply(this, arguments); + + var firstRendering = (this.tile == null); + + if(zoomChanged || firstRendering) { + + //determine new tile size + this.setTileSize(); + + //determine new position (upper left corner of new bounds) + var ulPx = this.map.getLayerPxFromLonLat({ + lon: this.extent.left, + lat: this.extent.top + }); + + if(firstRendering) { + //create the new tile + this.tile = new OpenLayers.Tile.Image(this, ulPx, this.extent, + null, this.tileSize); + this.addTileMonitoringHooks(this.tile); + } else { + //just resize the tile and set it's new position + this.tile.size = this.tileSize.clone(); + this.tile.position = ulPx.clone(); + } + this.tile.draw(); + } + }, + + /** + * Set the tile size based on the map size. + */ + setTileSize: function() { + var tileWidth = this.extent.getWidth() / this.map.getResolution(); + var tileHeight = this.extent.getHeight() / this.map.getResolution(); + this.tileSize = new OpenLayers.Size(tileWidth, tileHeight); + }, + + /** + * 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) { + tile.onLoadStart = function() { + this.events.triggerEvent("loadstart"); + }; + tile.events.register("loadstart", this, tile.onLoadStart); + + tile.onLoadEnd = function() { + this.events.triggerEvent("loadend"); + }; + tile.events.register("loadend", this, tile.onLoadEnd); + tile.events.register("unload", this, tile.onLoadEnd); + }, + + /** + * Method: removeTileMonitoringHooks + * This function takes a tile as input and removes the tile hooks + * that were added in . + * + * Parameters: + * tile - {} + */ + removeTileMonitoringHooks: function(tile) { + tile.unload(); + tile.events.un({ + "loadstart": tile.onLoadStart, + "loadend": tile.onLoadEnd, + "unload": tile.onLoadEnd, + scope: this + }); + }, + + /** + * APIMethod: setUrl + * + * Parameters: + * newUrl - {String} + */ + setUrl: function(newUrl) { + this.url = newUrl; + this.tile.draw(); + }, + + /** + * APIMethod: getURL + * The url we return is always the same (the image itself never changes) + * so we can ignore the bounds parameter (it will always be the same, + * anyways) + * + * Parameters: + * bounds - {} + */ + getURL: function(bounds) { + return this.url; + }, + + CLASS_NAME: "OpenLayers.Layer.Image" +}); +/* ====================================================================== + 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/Save.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.Save + * A strategy that commits newly created or modified features. By default + * the strategy waits for a call to before persisting changes. By + * configuring the strategy with the option, changes can be saved + * automatically. + * + * Inherits from: + * - + */ +OpenLayers.Strategy.Save = OpenLayers.Class(OpenLayers.Strategy, { + + /** + * APIProperty: events + * {} An events object that handles all + * events on the strategy object. + * + * Register a listener for a particular event with the following syntax: + * (code) + * strategy.events.register(type, obj, listener); + * (end) + * + * Supported event types: + * start - Triggered before saving + * success - Triggered after a successful transaction + * fail - Triggered after a failed transaction + * + */ + + /** + * Property: events + * {} Events instance for triggering this protocol + * events. + */ + events: null, + + /** + * APIProperty: auto + * {Boolean | Number} Auto-save. Default is false. If true, features will be + * saved immediately after being added to the layer and with each + * modification or deletion. If auto is a number, features will be + * saved on an interval provided by the value (in seconds). + */ + auto: false, + + /** + * Property: timer + * {Number} The id of the timer. + */ + timer: null, + + /** + * Constructor: OpenLayers.Strategy.Save + * Create a new Save strategy. + * + * Parameters: + * options - {Object} Optional object whose properties will be set on the + * instance. + */ + initialize: function(options) { + OpenLayers.Strategy.prototype.initialize.apply(this, [options]); + this.events = new OpenLayers.Events(this); + }, + + /** + * APIMethod: activate + * Activate the strategy. Register any listeners, do appropriate setup. + * + * Returns: + * {Boolean} The strategy was successfully activated. + */ + activate: function() { + var activated = OpenLayers.Strategy.prototype.activate.call(this); + if(activated) { + if(this.auto) { + if(typeof this.auto === "number") { + this.timer = window.setInterval( + OpenLayers.Function.bind(this.save, this), + this.auto * 1000 + ); + } else { + this.layer.events.on({ + "featureadded": this.triggerSave, + "afterfeaturemodified": this.triggerSave, + scope: this + }); + } + } + } + return activated; + }, + + /** + * APIMethod: deactivate + * Deactivate the strategy. Unregister any listeners, do appropriate + * tear-down. + * + * Returns: + * {Boolean} The strategy was successfully deactivated. + */ + deactivate: function() { + var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this); + if(deactivated) { + if(this.auto) { + if(typeof this.auto === "number") { + window.clearInterval(this.timer); + } else { + this.layer.events.un({ + "featureadded": this.triggerSave, + "afterfeaturemodified": this.triggerSave, + scope: this + }); + } + } + } + return deactivated; + }, + + /** + * Method: triggerSave + * Registered as a listener. Calls save if a feature has insert, update, + * or delete state. + * + * Parameters: + * event - {Object} The event this function is listening for. + */ + triggerSave: function(event) { + var feature = event.feature; + if(feature.state === OpenLayers.State.INSERT || + feature.state === OpenLayers.State.UPDATE || + feature.state === OpenLayers.State.DELETE) { + this.save([event.feature]); + } + }, + + /** + * APIMethod: save + * Tell the layer protocol to commit unsaved features. If the layer + * projection differs from the map projection, features will be + * transformed into the layer projection before being committed. + * + * Parameters: + * features - {Array} Features to be saved. If null, then default is all + * features in the layer. Features are assumed to be in the map + * projection. + */ + save: function(features) { + if(!features) { + features = this.layer.features; + } + this.events.triggerEvent("start", {features:features}); + var remote = this.layer.projection; + var local = this.layer.map.getProjectionObject(); + if(!local.equals(remote)) { + var len = features.length; + var clones = new Array(len); + var orig, clone; + for(var i=0; i} A response object. + */ + onCommit: function(response) { + var evt = {"response": response}; + if(response.success()) { + var features = response.reqFeatures; + // deal with inserts, updates, and deletes + var state, feature; + var destroys = []; + var insertIds = response.insertIds || []; + var j = 0; + for(var i=0, len=features.length; i 0) { + this.layer.destroyFeatures(destroys); + } + + this.events.triggerEvent("success", evt); + + } else { + this.events.triggerEvent("fail", evt); + } + }, + + CLASS_NAME: "OpenLayers.Strategy.Save" +}); +/* ====================================================================== + OpenLayers/Events/featureclick.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 + */ + +/** + * Class: OpenLayers.Events.featureclick + * + * Extension event type for handling feature click events, including overlapping + * features. + * + * Event types provided by this extension: + * - featureclick + */ +OpenLayers.Events.featureclick = OpenLayers.Class({ + + /** + * Property: cache + * {Object} A cache of features under the mouse. + */ + cache: null, + + /** + * Property: map + * {} The map to register browser events on. + */ + map: null, + + /** + * Property: provides + * {Array(String)} The event types provided by this extension. + */ + provides: ["featureclick", "nofeatureclick", "featureover", "featureout"], + + /** + * Constructor: OpenLayers.Events.featureclick + * Create a new featureclick event type. + * + * Parameters: + * target - {} The events instance to create the events + * for. + */ + initialize: function(target) { + this.target = target; + if (target.object instanceof OpenLayers.Map) { + this.setMap(target.object); + } else if (target.object instanceof OpenLayers.Layer.Vector) { + if (target.object.map) { + this.setMap(target.object.map); + } else { + target.object.events.register("added", this, function(evt) { + this.setMap(target.object.map); + }); + } + } else { + throw("Listeners for '" + this.provides.join("', '") + + "' events can only be registered for OpenLayers.Layer.Vector " + + "or OpenLayers.Map instances"); + } + for (var i=this.provides.length-1; i>=0; --i) { + target.extensions[this.provides[i]] = true; + } + }, + + /** + * Method: setMap + * + * Parameters: + * map - {} The map to register browser events on. + */ + setMap: function(map) { + this.map = map; + this.cache = {}; + map.events.register("mousedown", this, this.start, {extension: true}); + map.events.register("mouseup", this, this.onClick, {extension: true}); + map.events.register("touchstart", this, this.start, {extension: true}); + map.events.register("touchmove", this, this.cancel, {extension: true}); + map.events.register("touchend", this, this.onClick, {extension: true}); + map.events.register("mousemove", this, this.onMousemove, {extension: true}); + }, + + /** + * Method: start + * Sets startEvt = evt. + * + * Parameters: + * evt - {} + */ + start: function(evt) { + this.startEvt = evt; + }, + + /** + * Method: cancel + * Deletes the start event. + * + * Parameters: + * evt - {} + */ + cancel: function(evt) { + delete this.startEvt; + }, + + /** + * Method: onClick + * Listener for the click event. + * + * Parameters: + * evt - {} + */ + onClick: function(evt) { + if (!this.startEvt || evt.type !== "touchend" && + !OpenLayers.Event.isLeftClick(evt)) { + return; + } + var features = this.getFeatures(this.startEvt); + delete this.startEvt; + // fire featureclick events + var feature, layer, more, clicked = {}; + for (var i=0, len=features.length; i} + */ + onMousemove: function(evt) { + delete this.startEvt; + var features = this.getFeatures(evt); + var over = {}, newly = [], feature; + for (var i=0, len=features.length; i)} List of features at the given point. + */ + getFeatures: function(evt) { + var x = evt.clientX, y = evt.clientY, + features = [], targets = [], layers = [], + layer, target, feature, i, len; + // go through all layers looking for targets + for (i=this.map.layers.length-1; i>=0; --i) { + layer = this.map.layers[i]; + if (layer.div.style.display !== "none") { + if (layer.renderer instanceof OpenLayers.Renderer.Elements) { + if (layer instanceof OpenLayers.Layer.Vector) { + target = document.elementFromPoint(x, y); + while (target && target._featureId) { + feature = layer.getFeatureById(target._featureId); + if (feature) { + features.push(feature); + target.style.display = "none"; + targets.push(target); + target = document.elementFromPoint(x, y); + } else { + // sketch, all bets off + target = false; + } + } + } + layers.push(layer); + layer.div.style.display = "none"; + } else if (layer.renderer instanceof OpenLayers.Renderer.Canvas) { + feature = layer.renderer.getFeatureIdFromEvent(evt); + if (feature) { + features.push(feature); + layers.push(layer); + } + } + } + } + // restore feature visibility + for (i=0, len=targets.length; i=0; --i) { + layers[i].div.style.display = "block"; + } + return features; + }, + + /** + * APIMethod: destroy + * Clean up. + */ + destroy: function() { + for (var i=this.provides.length-1; i>=0; --i) { + delete this.target.extensions[this.provides[i]]; + } + this.map.events.un({ + mousemove: this.onMousemove, + mousedown: this.start, + mouseup: this.onClick, + touchstart: this.start, + touchmove: this.cancel, + touchend: this.onClick, + scope: this + }); + delete this.cache; + delete this.map; + delete this.target; + } + +}); + +/** + * Class: OpenLayers.Events.nofeatureclick + * + * Extension event type for handling click events that do not hit a feature. + * + * Event types provided by this extension: + * - nofeatureclick + */ +OpenLayers.Events.nofeatureclick = OpenLayers.Events.featureclick; + +/** + * Class: OpenLayers.Events.featureover + * + * Extension event type for handling hovering over a feature. + * + * Event types provided by this extension: + * - featureover + */ +OpenLayers.Events.featureover = OpenLayers.Events.featureclick; + +/** + * Class: OpenLayers.Events.featureout + * + * Extension event type for handling leaving a feature. + * + * Event types provided by this extension: + * - featureout + */ +OpenLayers.Events.featureout = OpenLayers.Events.featureclick; +/* ====================================================================== + OpenLayers/Format/GPX.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/LineString.js + * @requires OpenLayers/Projection.js + */ + +/** + * Class: OpenLayers.Format.GPX + * Read/write GPX parser. Create a new instance with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Format.GPX = OpenLayers.Class(OpenLayers.Format.XML, { + + + /** + * APIProperty: defaultDesc + * {String} Default description for the waypoints/tracks in the case + * where the feature has no "description" attribute. + * Default is "No description available". + */ + defaultDesc: "No description available", + + /** + * APIProperty: extractWaypoints + * {Boolean} Extract waypoints from GPX. (default: true) + */ + extractWaypoints: true, + + /** + * APIProperty: extractTracks + * {Boolean} Extract tracks from GPX. (default: true) + */ + extractTracks: true, + + /** + * APIProperty: extractRoutes + * {Boolean} Extract routes from GPX. (default: true) + */ + extractRoutes: true, + + /** + * APIProperty: extractAttributes + * {Boolean} Extract feature attributes from GPX. (default: true) + * NOTE: Attributes as part of extensions to the GPX standard may not + * be extracted. + */ + extractAttributes: true, + + /** + * Property: namespaces + * {Object} Mapping of namespace aliases to namespace URIs. + */ + namespaces: { + gpx: "http://www.topografix.com/GPX/1/1", + xsi: "http://www.w3.org/2001/XMLSchema-instance" + }, + + /** + * Property: schemaLocation + * {String} Schema location. Defaults to + * "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" + */ + schemaLocation: "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd", + + /** + * APIProperty: creator + * {String} The creator attribute to be added to the written GPX files. + * Defaults to "OpenLayers" + */ + creator: "OpenLayers", + + /** + * Constructor: OpenLayers.Format.GPX + * Create a new parser for GPX. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + initialize: function(options) { + // GPX coordinates are always in longlat WGS84 + this.externalProjection = new OpenLayers.Projection("EPSG:4326"); + + OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); + }, + + /** + * APIMethod: read + * Return a list of features from a GPX doc + * + * Parameters: + * doc - {Element} + * + * Returns: + * Array({}) + */ + read: function(doc) { + if (typeof doc == "string") { + doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]); + } + var features = []; + + if(this.extractTracks) { + var tracks = doc.getElementsByTagName("trk"); + for (var i=0, len=tracks.length; i} A linestring geometry + */ + extractSegment: function(segment, segmentType) { + var points = this.getElementsByTagNameNS(segment, segment.namespaceURI, segmentType); + var point_features = []; + for (var i = 0, len = points.length; i < len; i++) { + point_features.push(new OpenLayers.Geometry.Point(points[i].getAttribute("lon"), points[i].getAttribute("lat"))); + } + return new OpenLayers.Geometry.LineString(point_features); + }, + + /** + * Method: parseAttributes + * + * Parameters: + * node - {} + * + * Returns: + * {Object} An attributes object. + */ + parseAttributes: function(node) { + // node is either a wpt, trk or rte + // attributes are children of the form value + var attributes = {}; + var attrNode = node.firstChild, value, name; + while(attrNode) { + if(attrNode.nodeType == 1 && attrNode.firstChild) { + value = attrNode.firstChild; + if(value.nodeType == 3 || value.nodeType == 4) { + name = (attrNode.prefix) ? + attrNode.nodeName.split(":")[1] : + attrNode.nodeName; + if(name != "trkseg" && name != "rtept") { + attributes[name] = value.nodeValue; + } + } + } + attrNode = attrNode.nextSibling; + } + return attributes; + }, + + /** + * APIMethod: write + * Accepts Feature Collection, and returns a string. + * + * Parameters: + * features - {Array()} List of features to serialize into a string. + * metadata - {Object} A key/value pairs object to build a metadata node to + * add to the gpx. Supported keys are 'name', 'desc', 'author'. + */ + write: function(features, metadata) { + features = OpenLayers.Util.isArray(features) ? + features : [features]; + var gpx = this.createElementNS(this.namespaces.gpx, "gpx"); + gpx.setAttribute("version", "1.1"); + gpx.setAttribute("creator", this.creator); + this.setAttributes(gpx, { + "xsi:schemaLocation": this.schemaLocation + }); + + if (metadata && typeof metadata == 'object') { + gpx.appendChild(this.buildMetadataNode(metadata)); + } + for(var i=0, len=features.length; i, and builds a node for it. + * + * Parameters: + * feature - {} + * + * Returns: + * {DOMElement} - The created node, either a 'wpt' or a 'trk'. + */ + buildFeatureNode: function(feature) { + var geometry = feature.geometry; + geometry = geometry.clone(); + if (this.internalProjection && this.externalProjection) { + geometry.transform(this.internalProjection, + this.externalProjection); + } + if (geometry.CLASS_NAME == "OpenLayers.Geometry.Point") { + var wpt = this.buildWptNode(geometry); + this.appendAttributesNode(wpt, feature); + return wpt; + } else { + var trkNode = this.createElementNS(this.namespaces.gpx, "trk"); + this.appendAttributesNode(trkNode, feature); + var trkSegNodes = this.buildTrkSegNode(geometry); + trkSegNodes = OpenLayers.Util.isArray(trkSegNodes) ? + trkSegNodes : [trkSegNodes]; + for (var i = 0, len = trkSegNodes.length; i < len; i++) { + trkNode.appendChild(trkSegNodes[i]); + } + return trkNode; + } + }, + + /** + * Method: buildTrkSegNode + * Builds trkseg node(s) given a geometry + * + * Parameters: + * trknode + * geometry - {} + */ + buildTrkSegNode: function(geometry) { + var node, + i, + len, + point, + nodes; + if (geometry.CLASS_NAME == "OpenLayers.Geometry.LineString" || + geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") { + node = this.createElementNS(this.namespaces.gpx, "trkseg"); + for (i = 0, len=geometry.components.length; i < len; i++) { + point = geometry.components[i]; + node.appendChild(this.buildTrkPtNode(point)); + } + return node; + } else { + nodes = []; + for (i = 0, len = geometry.components.length; i < len; i++) { + nodes.push(this.buildTrkSegNode(geometry.components[i])); + } + return nodes; + } + }, + + /** + * Method: buildTrkPtNode + * Builds a trkpt node given a point + * + * Parameters: + * point - {} + * + * Returns: + * {DOMElement} A trkpt node + */ + buildTrkPtNode: function(point) { + var node = this.createElementNS(this.namespaces.gpx, "trkpt"); + node.setAttribute("lon", point.x); + node.setAttribute("lat", point.y); + return node; + }, + + /** + * Method: buildWptNode + * Builds a wpt node given a point + * + * Parameters: + * geometry - {} + * + * Returns: + * {DOMElement} A wpt node + */ + buildWptNode: function(geometry) { + var node = this.createElementNS(this.namespaces.gpx, "wpt"); + node.setAttribute("lon", geometry.x); + node.setAttribute("lat", geometry.y); + return node; + }, + + /** + * Method: appendAttributesNode + * Adds some attributes node. + * + * Parameters: + * node - {DOMElement} the node to append the attribute nodes to. + * feature - {} + */ + appendAttributesNode: function(node, feature) { + var name = this.createElementNS(this.namespaces.gpx, 'name'); + name.appendChild(this.createTextNode( + feature.attributes.name || feature.id)); + node.appendChild(name); + var desc = this.createElementNS(this.namespaces.gpx, 'desc'); + desc.appendChild(this.createTextNode( + feature.attributes.description || this.defaultDesc)); + node.appendChild(desc); + // TBD - deal with remaining (non name/description) attributes. + }, + + CLASS_NAME: "OpenLayers.Format.GPX" +}); +/* ====================================================================== + OpenLayers/Format/WMSDescribeLayer.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 + */ + +/** + * Class: OpenLayers.Format.WMSDescribeLayer + * Read SLD WMS DescribeLayer response + * DescribeLayer is meant to couple WMS to WFS and WCS + * + * Inherits from: + * - + */ +OpenLayers.Format.WMSDescribeLayer = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, { + + /** + * APIProperty: defaultVersion + * {String} Version number to assume if none found. Default is "1.1.1". + */ + defaultVersion: "1.1.1", + + /** + * Constructor: OpenLayers.Format.WMSDescribeLayer + * Create a new parser for WMS DescribeLayer responses. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + + /** + * APIMethod: read + * Read DescribeLayer data from a string, and return the response. + * The OGC currently defines 2 formats which are allowed for output, + * so we need to parse these 2 types + * + * Parameters: + * data - {String} or {DOMElement} data to read/parse. + * + * Returns: + * {Array} Array of {} objects which have: + * - {String} owsType: WFS/WCS + * - {String} owsURL: the online resource + * - {String} typeName: the name of the typename on the service + */ + + CLASS_NAME: "OpenLayers.Format.WMSDescribeLayer" + +}); +/* ====================================================================== + OpenLayers/Format/WMSDescribeLayer/v1_1.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/WMSDescribeLayer.js + * @requires OpenLayers/Format/OGCExceptionReport.js + */ + +/** + * Class: OpenLayers.Format.WMSDescribeLayer.v1_1_1 + * Read SLD WMS DescribeLayer response for WMS 1.1.X + * WMS 1.1.X is tightly coupled to SLD 1.0.0 + * + * Example DescribeLayer request: + * http://demo.opengeo.org/geoserver/wms?request=DescribeLayer&version=1.1.1&layers=topp:states + * + * Inherits from: + * - + */ +OpenLayers.Format.WMSDescribeLayer.v1_1_1 = OpenLayers.Class( + OpenLayers.Format.WMSDescribeLayer, { + + /** + * Constructor: OpenLayers.Format.WMSDescribeLayer + * Create a new parser for WMS DescribeLayer responses. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + initialize: function(options) { + OpenLayers.Format.WMSDescribeLayer.prototype.initialize.apply(this, + [options]); + }, + + /** + * APIMethod: read + * Read DescribeLayer data from a string, and return the response. + * The OGC defines 2 formats which are allowed for output, + * so we need to parse these 2 types for version 1.1.X + * + * Parameters: + * data - {String} or {DOMElement} data to read/parse. + * + * Returns: + * {Object} Object with a layerDescriptions property, which holds an Array + * of {} objects which have: + * - {String} owsType: WFS/WCS + * - {String} owsURL: the online resource + * - {String} typeName: the name of the typename on the owsType service + * - {String} layerName: the name of the WMS layer we did a lookup for + */ + read: function(data) { + if(typeof data == "string") { + data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); + } + var root = data.documentElement; + var children = root.childNodes; + var describelayer = {layerDescriptions: []}; + var childNode, nodeName; + for(var i=0; i 0) { + typeName = query[0].getAttribute('typeName'); + if (!typeName) { + // because of Ionic bug + typeName = query[0].getAttribute('typename'); + } + } + var layerDescription = { + layerName: layerName, owsType: owsType, + owsURL: owsURL, typeName: typeName + }; + describelayer.layerDescriptions.push(layerDescription); + + //TODO do this in deprecated.js instead: + // array style index for backwards compatibility + describelayer.length = describelayer.layerDescriptions.length; + describelayer[describelayer.length - 1] = layerDescription; + + } else if (nodeName == 'ServiceException') { + // an exception must have occurred, so parse it + var parser = new OpenLayers.Format.OGCExceptionReport(); + return { + error: parser.read(data) + }; + } + } + return describelayer; + }, + + CLASS_NAME: "OpenLayers.Format.WMSDescribeLayer.v1_1_1" + +}); + +// Version alias - workaround for http://trac.osgeo.org/mapserver/ticket/2257 +OpenLayers.Format.WMSDescribeLayer.v1_1_0 = + OpenLayers.Format.WMSDescribeLayer.v1_1_1; +/* ====================================================================== + 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 constructor. + * + * Inherits from: + * - + */ +OpenLayers.Format.OSM = OpenLayers.Class(OpenLayers.Format.XML, { + + /** + * APIProperty: checkTags + * {Boolean} Should tags be checked to determine whether something + * should be treated as a seperate node. Will slow down parsing. + * Default is false. + */ + checkTags: false, + + /** + * Property: interestingTagsExclude + * {Array} List of tags to exclude from 'interesting' checks on nodes. + * Must be set when creating the format. Will only be used if checkTags + * is set. + */ + interestingTagsExclude: null, + + /** + * APIProperty: areaTags + * {Array} List of tags indicating that something is an area. + * Must be set when creating the format. Will only be used if + * checkTags is true. + */ + areaTags: null, + + /** + * Constructor: OpenLayers.Format.OSM + * Create a new parser for OSM. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + initialize: function(options) { + var layer_defaults = { + 'interestingTagsExclude': ['source', 'source_ref', + 'source:ref', 'history', 'attribution', 'created_by'], + 'areaTags': ['area', 'building', 'leisure', 'tourism', 'ruins', + 'historic', 'landuse', 'military', 'natural', 'sport'] + }; + + layer_defaults = OpenLayers.Util.extend(layer_defaults, options); + + var interesting = {}; + for (var i = 0; i < layer_defaults.interestingTagsExclude.length; i++) { + interesting[layer_defaults.interestingTagsExclude[i]] = true; + } + layer_defaults.interestingTagsExclude = interesting; + + var area = {}; + for (var i = 0; i < layer_defaults.areaTags.length; i++) { + area[layer_defaults.areaTags[i]] = true; + } + layer_defaults.areaTags = area; + + // OSM coordinates are always in longlat WGS84 + this.externalProjection = new OpenLayers.Projection("EPSG:4326"); + + OpenLayers.Format.XML.prototype.initialize.apply(this, [layer_defaults]); + }, + + /** + * APIMethod: read + * Return a list of features from a OSM doc + + * Parameters: + * doc - {Element} + * + * Returns: + * Array({}) + */ + read: function(doc) { + if (typeof doc == "string") { + doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]); + } + + var nodes = this.getNodes(doc); + var ways = this.getWays(doc); + + // Geoms will contain at least ways.length entries. + var feat_list = new Array(ways.length); + + for (var i = 0; i < ways.length; i++) { + // We know the minimal of this one ahead of time. (Could be -1 + // due to areas/polygons) + var point_list = new Array(ways[i].nodes.length); + + var poly = this.isWayArea(ways[i]) ? 1 : 0; + for (var j = 0; j < ways[i].nodes.length; j++) { + var node = nodes[ways[i].nodes[j]]; + + var point = new OpenLayers.Geometry.Point(node.lon, node.lat); + + // Since OSM is topological, we stash the node ID internally. + point.osm_id = parseInt(ways[i].nodes[j]); + point_list[j] = point; + + // We don't display nodes if they're used inside other + // elements. + node.used = true; + } + var geometry = null; + if (poly) { + geometry = new OpenLayers.Geometry.Polygon( + new OpenLayers.Geometry.LinearRing(point_list)); + } else { + geometry = new OpenLayers.Geometry.LineString(point_list); + } + if (this.internalProjection && this.externalProjection) { + geometry.transform(this.externalProjection, + this.internalProjection); + } + var feat = new OpenLayers.Feature.Vector(geometry, + ways[i].tags); + feat.osm_id = parseInt(ways[i].id); + feat.fid = "way." + feat.osm_id; + feat_list[i] = feat; + } + for (var node_id in nodes) { + var node = nodes[node_id]; + if (!node.used || this.checkTags) { + var tags = null; + + if (this.checkTags) { + var result = this.getTags(node.node, true); + if (node.used && !result[1]) { + continue; + } + tags = result[0]; + } else { + tags = this.getTags(node.node); + } + + var feat = new OpenLayers.Feature.Vector( + new OpenLayers.Geometry.Point(node['lon'], node['lat']), + tags); + if (this.internalProjection && this.externalProjection) { + feat.geometry.transform(this.externalProjection, + this.internalProjection); + } + feat.osm_id = parseInt(node_id); + feat.fid = "node." + feat.osm_id; + feat_list.push(feat); + } + // Memory cleanup + node.node = null; + } + return feat_list; + }, + + /** + * Method: getNodes + * Return the node items from a doc. + * + * Parameters: + * doc - {DOMElement} node to parse tags from + */ + getNodes: function(doc) { + var node_list = doc.getElementsByTagName("node"); + var nodes = {}; + for (var i = 0; i < node_list.length; i++) { + var node = node_list[i]; + var id = node.getAttribute("id"); + nodes[id] = { + 'lat': node.getAttribute("lat"), + 'lon': node.getAttribute("lon"), + 'node': node + }; + } + return nodes; + }, + + /** + * Method: getWays + * Return the way items from a doc. + * + * Parameters: + * doc - {DOMElement} node to parse tags from + */ + getWays: function(doc) { + var way_list = doc.getElementsByTagName("way"); + var return_ways = []; + for (var i = 0; i < way_list.length; i++) { + var way = way_list[i]; + var way_object = { + id: way.getAttribute("id") + }; + + way_object.tags = this.getTags(way); + + var node_list = way.getElementsByTagName("nd"); + + way_object.nodes = new Array(node_list.length); + + for (var j = 0; j < node_list.length; j++) { + way_object.nodes[j] = node_list[j].getAttribute("ref"); + } + return_ways.push(way_object); + } + return return_ways; + + }, + + /** + * Method: getTags + * Return the tags list attached to a specific DOM element. + * + * Parameters: + * dom_node - {DOMElement} node to parse tags from + * interesting_tags - {Boolean} whether the return from this function should + * return a boolean indicating that it has 'interesting tags' -- + * tags like attribution and source are ignored. (To change the list + * of tags, see interestingTagsExclude) + * + * Returns: + * tags - {Object} hash of tags + * interesting - {Boolean} if interesting_tags is passed, returns + * whether there are any interesting tags on this element. + */ + getTags: function(dom_node, interesting_tags) { + var tag_list = dom_node.getElementsByTagName("tag"); + var tags = {}; + var interesting = false; + for (var j = 0; j < tag_list.length; j++) { + var key = tag_list[j].getAttribute("k"); + tags[key] = tag_list[j].getAttribute("v"); + if (interesting_tags) { + if (!this.interestingTagsExclude[key]) { + interesting = true; + } + } + } + return interesting_tags ? [tags, interesting] : tags; + }, + + /** + * Method: isWayArea + * Given a way object from getWays, check whether the tags and geometry + * indicate something is an area. + * + * Returns: + * {Boolean} + */ + isWayArea: function(way) { + var poly_shaped = false; + var poly_tags = false; + + if (way.nodes[0] == way.nodes[way.nodes.length - 1]) { + poly_shaped = true; + } + if (this.checkTags) { + for(var key in way.tags) { + if (this.areaTags[key]) { + poly_tags = true; + break; + } + } + } + return poly_shaped && (this.checkTags ? poly_tags : true); + }, + + /** + * APIMethod: write + * Takes a list of features, returns a serialized OSM format file for use + * in tools like JOSM. + * + * Parameters: + * features - {Array()} + */ + write: function(features) { + if (!(OpenLayers.Util.isArray(features))) { + features = [features]; + } + + this.osm_id = 1; + this.created_nodes = {}; + var root_node = this.createElementNS(null, "osm"); + root_node.setAttribute("version", "0.5"); + root_node.setAttribute("generator", "OpenLayers "+ OpenLayers.VERSION_NUMBER); + + // Loop backwards, because the deserializer puts nodes last, and + // we want them first if possible + for(var i = features.length - 1; i >= 0; i--) { + var nodes = this.createFeatureNodes(features[i]); + for (var j = 0; j < nodes.length; j++) { + root_node.appendChild(nodes[j]); + } + } + return OpenLayers.Format.XML.prototype.write.apply(this, [root_node]); + }, + + /** + * Method: createFeatureNodes + * Takes a feature, returns a list of nodes from size 0->n. + * Will include all pieces of the serialization that are required which + * have not already been created. Calls out to createXML based on geometry + * type. + * + * Parameters: + * feature - {} + */ + createFeatureNodes: function(feature) { + var nodes = []; + var className = feature.geometry.CLASS_NAME; + var type = className.substring(className.lastIndexOf(".") + 1); + type = type.toLowerCase(); + var builder = this.createXML[type]; + if (builder) { + nodes = builder.apply(this, [feature]); + } + return nodes; + }, + + /** + * Method: createXML + * Takes a feature, returns a list of nodes from size 0->n. + * Will include all pieces of the serialization that are required which + * have not already been created. + * + * Parameters: + * feature - {} + */ + createXML: { + 'point': function(point) { + var id = null; + var geometry = point.geometry ? point.geometry : point; + + if (this.internalProjection && this.externalProjection) { + geometry = geometry.clone(); + geometry.transform(this.internalProjection, + this.externalProjection); + } + + var already_exists = false; // We don't return anything if the node + // has already been created + if (point.osm_id) { + id = point.osm_id; + if (this.created_nodes[id]) { + already_exists = true; + } + } else { + id = -this.osm_id; + this.osm_id++; + } + if (already_exists) { + node = this.created_nodes[id]; + } else { + var node = this.createElementNS(null, "node"); + } + this.created_nodes[id] = node; + node.setAttribute("id", id); + node.setAttribute("lon", geometry.x); + node.setAttribute("lat", geometry.y); + if (point.attributes) { + this.serializeTags(point, node); + } + this.setState(point, node); + return already_exists ? [] : [node]; + }, + linestring: function(feature) { + var id; + var nodes = []; + var geometry = feature.geometry; + if (feature.osm_id) { + id = feature.osm_id; + } else { + id = -this.osm_id; + this.osm_id++; + } + var way = this.createElementNS(null, "way"); + way.setAttribute("id", id); + for (var i = 0; i < geometry.components.length; i++) { + var node = this.createXML['point'].apply(this, [geometry.components[i]]); + if (node.length) { + node = node[0]; + var node_ref = node.getAttribute("id"); + nodes.push(node); + } else { + node_ref = geometry.components[i].osm_id; + node = this.created_nodes[node_ref]; + } + this.setState(feature, node); + var nd_dom = this.createElementNS(null, "nd"); + nd_dom.setAttribute("ref", node_ref); + way.appendChild(nd_dom); + } + this.serializeTags(feature, way); + nodes.push(way); + + return nodes; + }, + polygon: function(feature) { + var attrs = OpenLayers.Util.extend({'area':'yes'}, feature.attributes); + var feat = new OpenLayers.Feature.Vector(feature.geometry.components[0], attrs); + feat.osm_id = feature.osm_id; + return this.createXML['linestring'].apply(this, [feat]); + } + }, + + /** + * Method: serializeTags + * Given a feature, serialize the attributes onto the given node. + * + * Parameters: + * feature - {} + * node - {DOMNode} + */ + serializeTags: function(feature, node) { + for (var key in feature.attributes) { + var tag = this.createElementNS(null, "tag"); + tag.setAttribute("k", key); + tag.setAttribute("v", feature.attributes[key]); + node.appendChild(tag); + } + }, + + /** + * Method: setState + * OpenStreetMap has a convention that 'state' is stored for modification or deletion. + * This allows the file to be uploaded via JOSM or the bulk uploader tool. + * + * Parameters: + * feature - {} + * node - {DOMNode} + */ + setState: function(feature, node) { + if (feature.state) { + var state = null; + switch(feature.state) { + case OpenLayers.State.UPDATE: + state = "modify"; + case OpenLayers.State.DELETE: + state = "delete"; + } + if (state) { + node.setAttribute("action", state); + } + } + }, + + CLASS_NAME: "OpenLayers.Format.OSM" +}); +/* ====================================================================== + 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/StyleMap.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/Style.js + * @requires OpenLayers/Feature/Vector.js + */ + +/** + * Class: OpenLayers.StyleMap + */ +OpenLayers.StyleMap = OpenLayers.Class({ + + /** + * Property: styles + * {Object} Hash of {}, keyed by names of well known + * rendering intents (e.g. "default", "temporary", "select", "delete"). + */ + styles: null, + + /** + * Property: extendDefault + * {Boolean} if true, every render intent will extend the symbolizers + * specified for the "default" intent at rendering time. Otherwise, every + * rendering intent will be treated as a completely independent style. + */ + extendDefault: true, + + /** + * Constructor: OpenLayers.StyleMap + * + * Parameters: + * style - {Object} Optional. Either a style hash, or a style object, or + * a hash of style objects (style hashes) keyed by rendering + * intent. If just one style hash or style object is passed, + * this will be used for all known render intents (default, + * select, temporary) + * options - {Object} optional hash of additional options for this + * instance + */ + initialize: function (style, options) { + this.styles = { + "default": new OpenLayers.Style( + OpenLayers.Feature.Vector.style["default"]), + "select": new OpenLayers.Style( + OpenLayers.Feature.Vector.style["select"]), + "temporary": new OpenLayers.Style( + OpenLayers.Feature.Vector.style["temporary"]), + "delete": new OpenLayers.Style( + OpenLayers.Feature.Vector.style["delete"]) + }; + + // take whatever the user passed as style parameter and convert it + // into parts of stylemap. + if(style instanceof OpenLayers.Style) { + // user passed a style object + this.styles["default"] = style; + this.styles["select"] = style; + this.styles["temporary"] = style; + this.styles["delete"] = style; + } else if(typeof style == "object") { + for(var key in style) { + if(style[key] instanceof OpenLayers.Style) { + // user passed a hash of style objects + this.styles[key] = style[key]; + } else if(typeof style[key] == "object") { + // user passsed a hash of style hashes + this.styles[key] = new OpenLayers.Style(style[key]); + } else { + // user passed a style hash (i.e. symbolizer) + this.styles["default"] = new OpenLayers.Style(style); + this.styles["select"] = new OpenLayers.Style(style); + this.styles["temporary"] = new OpenLayers.Style(style); + this.styles["delete"] = new OpenLayers.Style(style); + break; + } + } + } + OpenLayers.Util.extend(this, options); + }, + + /** + * Method: destroy + */ + destroy: function() { + for(var key in this.styles) { + this.styles[key].destroy(); + } + this.styles = null; + }, + + /** + * Method: createSymbolizer + * Creates the symbolizer for a feature for a render intent. + * + * Parameters: + * feature - {} The feature to evaluate the rules + * of the intended style against. + * intent - {String} The intent determines the symbolizer that will be + * used to draw the feature. Well known intents are "default" + * (for just drawing the features), "select" (for selected + * features) and "temporary" (for drawing features). + * + * Returns: + * {Object} symbolizer hash + */ + createSymbolizer: function(feature, intent) { + if(!feature) { + feature = new OpenLayers.Feature.Vector(); + } + if(!this.styles[intent]) { + intent = "default"; + } + feature.renderIntent = intent; + var defaultSymbolizer = {}; + if(this.extendDefault && intent != "default") { + defaultSymbolizer = this.styles["default"].createSymbolizer(feature); + } + return OpenLayers.Util.extend(defaultSymbolizer, + this.styles[intent].createSymbolizer(feature)); + }, + + /** + * Method: addUniqueValueRules + * Convenience method to create comparison rules for unique values of a + * property. The rules will be added to the style object for a specified + * rendering intent. This method is a shortcut for creating something like + * the "unique value legends" familiar from well known desktop GIS systems + * + * Parameters: + * renderIntent - {String} rendering intent to add the rules to + * property - {String} values of feature attributes to create the + * rules for + * symbolizers - {Object} Hash of symbolizers, keyed by the desired + * property values + * 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 + */ + addUniqueValueRules: function(renderIntent, property, symbolizers, context) { + var rules = []; + for (var value in symbolizers) { + rules.push(new OpenLayers.Rule({ + symbolizer: symbolizers[value], + context: context, + filter: new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.EQUAL_TO, + property: property, + value: value + }) + })); + } + this.styles[renderIntent].addRules(rules); + }, + + CLASS_NAME: "OpenLayers.StyleMap" +}); +/* ====================================================================== + OpenLayers/Layer/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. */ + +/** + * @requires OpenLayers/Layer.js + * @requires OpenLayers/Renderer.js + * @requires OpenLayers/StyleMap.js + * @requires OpenLayers/Feature/Vector.js + * @requires OpenLayers/Console.js + * @requires OpenLayers/Lang.js + */ + +/** + * Class: OpenLayers.Layer.Vector + * Instances of OpenLayers.Layer.Vector are used to render vector data from + * a variety of sources. Create a new vector layer with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Layer.Vector = OpenLayers.Class(OpenLayers.Layer, { + + /** + * 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 (in addition to those from ): + * beforefeatureadded - Triggered before a feature is added. Listeners + * will receive an object with a *feature* property referencing the + * feature to be added. To stop the feature from being added, a + * listener should return false. + * beforefeaturesadded - Triggered before an array of features is added. + * Listeners will receive an object with a *features* property + * referencing the feature to be added. To stop the features from + * being added, a listener should return false. + * featureadded - Triggered after a feature is added. The event + * object passed to listeners will have a *feature* property with a + * reference to the added feature. + * featuresadded - Triggered after features are added. The event + * object passed to listeners will have a *features* property with a + * reference to an array of added features. + * beforefeatureremoved - Triggered before a feature is removed. Listeners + * will receive an object with a *feature* property referencing the + * feature to be removed. + * beforefeaturesremoved - Triggered before multiple features are removed. + * Listeners will receive an object with a *features* property + * referencing the features to be removed. + * featureremoved - Triggerd after a feature is removed. The event + * object passed to listeners will have a *feature* property with a + * reference to the removed feature. + * featuresremoved - Triggered after features are removed. The event + * object passed to listeners will have a *features* property with a + * reference to an array of removed features. + * beforefeatureselected - Triggered before a feature is selected. Listeners + * will receive an object with a *feature* property referencing the + * feature to be selected. To stop the feature from being selectd, a + * listener should return false. + * featureselected - Triggered after a feature is selected. Listeners + * will receive an object with a *feature* property referencing the + * selected feature. + * featureunselected - Triggered after a feature is unselected. + * Listeners will receive an object with a *feature* property + * referencing the unselected feature. + * beforefeaturemodified - Triggered when a feature is selected to + * be modified. Listeners will receive an object with a *feature* + * property referencing the selected feature. + * featuremodified - Triggered when a feature has been modified. + * Listeners will receive an object with a *feature* property referencing + * the modified feature. + * afterfeaturemodified - Triggered when a feature is finished being modified. + * Listeners will receive an object with a *feature* property referencing + * the modified feature. + * vertexmodified - Triggered when a vertex within any feature geometry + * has been modified. Listeners will receive an object with a + * *feature* property referencing the modified feature, a *vertex* + * property referencing the vertex modified (always a point geometry), + * and a *pixel* property referencing the pixel location of the + * modification. + * vertexremoved - Triggered when a vertex within any feature geometry + * has been deleted. Listeners will receive an object with a + * *feature* property referencing the modified feature, a *vertex* + * property referencing the vertex modified (always a point geometry), + * and a *pixel* property referencing the pixel location of the + * removal. + * sketchstarted - Triggered when a feature sketch bound for this layer + * is started. Listeners will receive an object with a *feature* + * property referencing the new sketch feature and a *vertex* property + * referencing the creation point. + * sketchmodified - Triggered when a feature sketch bound for this layer + * is modified. Listeners will receive an object with a *vertex* + * property referencing the modified vertex and a *feature* property + * referencing the sketch feature. + * sketchcomplete - Triggered when a feature sketch bound for this layer + * is complete. Listeners will receive an object with a *feature* + * property referencing the sketch feature. By returning false, a + * listener can stop the sketch feature from being added to the layer. + * refresh - Triggered when something wants a strategy to ask the protocol + * for a new set of features. + */ + + /** + * APIProperty: isBaseLayer + * {Boolean} The layer is a base layer. Default is false. Set this property + * in the layer options. + */ + isBaseLayer: false, + + /** + * APIProperty: isFixed + * {Boolean} Whether the layer remains in one place while dragging the + * map. Note that setting this to true will move the layer to the bottom + * of the layer stack. + */ + isFixed: false, + + /** + * APIProperty: features + * {Array()} + */ + features: null, + + /** + * Property: filter + * {} The filter set in this layer, + * a strategy launching read requests can combined + * this filter with its own filter. + */ + filter: null, + + /** + * Property: selectedFeatures + * {Array()} + */ + selectedFeatures: null, + + /** + * Property: unrenderedFeatures + * {Object} hash of features, keyed by feature.id, that the renderer + * failed to draw + */ + unrenderedFeatures: null, + + /** + * APIProperty: reportError + * {Boolean} report friendly error message when loading of renderer + * fails. + */ + reportError: true, + + /** + * APIProperty: style + * {Object} Default style for the layer + */ + style: null, + + /** + * Property: styleMap + * {} + */ + styleMap: null, + + /** + * Property: strategies + * {Array(})} Optional list of strategies for the layer. + */ + strategies: null, + + /** + * Property: protocol + * {} Optional protocol for the layer. + */ + protocol: null, + + /** + * Property: renderers + * {Array(String)} List of supported Renderer classes. Add to this list to + * add support for additional renderers. This list is ordered: + * the first renderer which returns true for the 'supported()' + * method will be used, if not defined in the 'renderer' option. + */ + renderers: ['SVG', 'VML', 'Canvas'], + + /** + * Property: renderer + * {} + */ + renderer: null, + + /** + * APIProperty: rendererOptions + * {Object} Options for the renderer. See {} for + * supported options. + */ + rendererOptions: null, + + /** + * APIProperty: geometryType + * {String} geometryType allows you to limit the types of geometries this + * layer supports. This should be set to something like + * "OpenLayers.Geometry.Point" to limit types. + */ + geometryType: null, + + /** + * Property: drawn + * {Boolean} Whether the Vector Layer features have been drawn yet. + */ + drawn: false, + + /** + * APIProperty: ratio + * {Float} This specifies the ratio of the size of the visiblity of the Vector Layer features to the size of the map. + */ + ratio: 1, + + /** + * Constructor: OpenLayers.Layer.Vector + * Create a new vector layer + * + * Parameters: + * name - {String} A name for the layer + * options - {Object} Optional object with non-default properties to set on + * the layer. + * + * Returns: + * {} A new vector layer + */ + initialize: function(name, options) { + OpenLayers.Layer.prototype.initialize.apply(this, arguments); + + // allow user-set renderer, otherwise assign one + if (!this.renderer || !this.renderer.supported()) { + this.assignRenderer(); + } + + // if no valid renderer found, display error + if (!this.renderer || !this.renderer.supported()) { + this.renderer = null; + this.displayError(); + } + + if (!this.styleMap) { + this.styleMap = new OpenLayers.StyleMap(); + } + + this.features = []; + this.selectedFeatures = []; + this.unrenderedFeatures = {}; + + // Allow for custom layer behavior + if(this.strategies){ + for(var i=0, len=this.strategies.length; i} An exact clone of this layer + */ + clone: function (obj) { + + if (obj == null) { + obj = new OpenLayers.Layer.Vector(this.name, this.getOptions()); + } + + //get all additions from superclasses + obj = OpenLayers.Layer.prototype.clone.apply(this, [obj]); + + // copy/set any non-init, non-simple values here + var features = this.features; + var len = features.length; + var clonedFeatures = new Array(len); + for(var i=0; i} + */ + setMap: function(map) { + OpenLayers.Layer.prototype.setMap.apply(this, arguments); + + if (!this.renderer) { + this.map.removeLayer(this); + } else { + this.renderer.map = this.map; + + var newSize = this.map.getSize(); + newSize.w = newSize.w * this.ratio; + newSize.h = newSize.h * this.ratio; + this.renderer.setSize(newSize); + } + }, + + /** + * Method: afterAdd + * Called at the end of the map.addLayer sequence. At this point, the map + * will have a base layer. Any autoActivate strategies will be + * activated here. + */ + afterAdd: function() { + if(this.strategies) { + var strategy, i, len; + for(i=0, len=this.strategies.length; i} + */ + removeMap: function(map) { + this.drawn = false; + if(this.strategies) { + var strategy, i, len; + for(i=0, len=this.strategies.length; i} + * zoomChanged - {Boolean} + * dragging - {Boolean} + */ + moveTo: function(bounds, zoomChanged, dragging) { + OpenLayers.Layer.prototype.moveTo.apply(this, arguments); + + var coordSysUnchanged = true; + if (!dragging) { + this.renderer.root.style.visibility = 'hidden'; + + var viewSize = this.map.getSize(), + viewWidth = viewSize.w, + viewHeight = viewSize.h, + offsetLeft = (viewWidth / 2 * this.ratio) - viewWidth / 2, + offsetTop = (viewHeight / 2 * this.ratio) - viewHeight / 2; + offsetLeft += this.map.layerContainerOriginPx.x; + offsetLeft = -Math.round(offsetLeft); + offsetTop += this.map.layerContainerOriginPx.y; + offsetTop = -Math.round(offsetTop); + + this.div.style.left = offsetLeft + 'px'; + this.div.style.top = offsetTop + 'px'; + + var extent = this.map.getExtent().scale(this.ratio); + coordSysUnchanged = this.renderer.setExtent(extent, zoomChanged); + + this.renderer.root.style.visibility = 'visible'; + + // Force a reflow on gecko based browsers to prevent jump/flicker. + // This seems to happen on only certain configurations; it was originally + // noticed in FF 2.0 and Linux. + if (OpenLayers.IS_GECKO === true) { + this.div.scrollLeft = this.div.scrollLeft; + } + + if (!zoomChanged && coordSysUnchanged) { + for (var i in this.unrenderedFeatures) { + var feature = this.unrenderedFeatures[i]; + this.drawFeature(feature); + } + } + } + if (!this.drawn || zoomChanged || !coordSysUnchanged) { + this.drawn = true; + var feature; + for(var i=0, len=this.features.length; i)} + * options - {Object} + */ + addFeatures: function(features, options) { + if (!(OpenLayers.Util.isArray(features))) { + features = [features]; + } + + var notify = !options || !options.silent; + if(notify) { + var event = {features: features}; + var ret = this.events.triggerEvent("beforefeaturesadded", event); + if(ret === false) { + return; + } + features = event.features; + } + + // Track successfully added features for featuresadded event, since + // beforefeatureadded can veto single features. + var featuresAdded = []; + for (var i=0, len=features.length; i)} List of features to be + * removed. + * options - {Object} Optional properties for changing behavior of the + * removal. + * + * Valid options: + * silent - {Boolean} Supress event triggering. Default is false. + */ + removeFeatures: function(features, options) { + if(!features || features.length === 0) { + return; + } + if (features === this.features) { + return this.removeAllFeatures(options); + } + if (!(OpenLayers.Util.isArray(features))) { + features = [features]; + } + if (features === this.selectedFeatures) { + features = features.slice(); + } + + var notify = !options || !options.silent; + + if (notify) { + this.events.triggerEvent( + "beforefeaturesremoved", {features: features} + ); + } + + for (var i = features.length - 1; i >= 0; i--) { + // We remain locked so long as we're not at 0 + // and the 'next' feature has a geometry. We do the geometry check + // because if all the features after the current one are 'null', we + // won't call eraseGeometry, so we break the 'renderer functions + // will always be called with locked=false *last*' rule. The end result + // is a possible gratiutious unlocking to save a loop through the rest + // of the list checking the remaining features every time. So long as + // null geoms are rare, this is probably okay. + if (i != 0 && features[i-1].geometry) { + this.renderer.locked = true; + } else { + this.renderer.locked = false; + } + + var feature = features[i]; + delete this.unrenderedFeatures[feature.id]; + + if (notify) { + this.events.triggerEvent("beforefeatureremoved", { + feature: feature + }); + } + + this.features = OpenLayers.Util.removeItem(this.features, feature); + // feature has no layer at this point + feature.layer = null; + + if (feature.geometry) { + this.renderer.eraseFeatures(feature); + } + + //in the case that this feature is one of the selected features, + // remove it from that array as well. + if (OpenLayers.Util.indexOf(this.selectedFeatures, feature) != -1){ + OpenLayers.Util.removeItem(this.selectedFeatures, feature); + } + + if (notify) { + this.events.triggerEvent("featureremoved", { + feature: feature + }); + } + } + + if (notify) { + this.events.triggerEvent("featuresremoved", {features: features}); + } + }, + + /** + * APIMethod: removeAllFeatures + * Remove all features from the layer. + * + * Parameters: + * options - {Object} Optional properties for changing behavior of the + * removal. + * + * Valid options: + * silent - {Boolean} Supress event triggering. Default is false. + */ + removeAllFeatures: function(options) { + var notify = !options || !options.silent; + var features = this.features; + if (notify) { + this.events.triggerEvent( + "beforefeaturesremoved", {features: features} + ); + } + var feature; + for (var i = features.length-1; i >= 0; i--) { + feature = features[i]; + if (notify) { + this.events.triggerEvent("beforefeatureremoved", { + feature: feature + }); + } + feature.layer = null; + if (notify) { + this.events.triggerEvent("featureremoved", { + feature: feature + }); + } + } + this.renderer.clear(); + this.features = []; + this.unrenderedFeatures = {}; + this.selectedFeatures = []; + if (notify) { + this.events.triggerEvent("featuresremoved", {features: features}); + } + }, + + /** + * APIMethod: destroyFeatures + * Erase and destroy features on the layer. + * + * Parameters: + * features - {Array()} An optional array of + * features to destroy. If not supplied, all features on the layer + * will be destroyed. + * options - {Object} + */ + destroyFeatures: function(features, options) { + var all = (features == undefined); // evaluates to true if + // features is null + if(all) { + features = this.features; + } + if(features) { + this.removeFeatures(features, options); + for(var i=features.length-1; i>=0; i--) { + features[i].destroy(); + } + } + }, + + /** + * APIMethod: drawFeature + * Draw (or redraw) a feature on the layer. If the optional style argument + * is included, this style will be used. If no style is included, the + * feature's style will be used. If the feature doesn't have a style, + * the layer's style will be used. + * + * This function is not designed to be used when adding features to + * the layer (use addFeatures instead). It is meant to be used when + * the style of a feature has changed, or in some other way needs to + * visually updated *after* it has already been added to a layer. You + * must add the feature to the layer for most layer-related events to + * happen. + * + * Parameters: + * feature - {} + * style - {String | Object} Named render intent or full symbolizer object. + */ + drawFeature: function(feature, style) { + // don't try to draw the feature with the renderer if the layer is not + // drawn itself + if (!this.drawn) { + return; + } + if (typeof style != "object") { + if(!style && feature.state === OpenLayers.State.DELETE) { + style = "delete"; + } + var renderIntent = style || feature.renderIntent; + style = feature.style || this.style; + if (!style) { + style = this.styleMap.createSymbolizer(feature, renderIntent); + } + } + + var drawn = this.renderer.drawFeature(feature, style); + //TODO remove the check for null when we get rid of Renderer.SVG + if (drawn === false || drawn === null) { + this.unrenderedFeatures[feature.id] = feature; + } else { + delete this.unrenderedFeatures[feature.id]; + } + }, + + /** + * Method: eraseFeatures + * Erase features from the layer. + * + * Parameters: + * features - {Array()} + */ + eraseFeatures: function(features) { + this.renderer.eraseFeatures(features); + }, + + /** + * Method: getFeatureFromEvent + * Given an event, return a feature if the event occurred over one. + * Otherwise, return null. + * + * Parameters: + * evt - {Event} + * + * Returns: + * {} A feature if one was under the event. + */ + getFeatureFromEvent: function(evt) { + if (!this.renderer) { + throw new Error('getFeatureFromEvent called on layer with no ' + + 'renderer. This usually means you destroyed a ' + + 'layer, but not some handler which is associated ' + + 'with it.'); + } + var feature = null; + var featureId = this.renderer.getFeatureIdFromEvent(evt); + if (featureId) { + if (typeof featureId === "string") { + feature = this.getFeatureById(featureId); + } else { + feature = featureId; + } + } + return feature; + }, + + /** + * APIMethod: getFeatureBy + * Given a property value, return the feature if it exists in the features array + * + * Parameters: + * property - {String} + * value - {String} + * + * Returns: + * {} A feature corresponding to the given + * property value or null if there is no such feature. + */ + getFeatureBy: function(property, value) { + //TBD - would it be more efficient to use a hash for this.features? + var feature = null; + for(var i=0, len=this.features.length; i} A feature corresponding to the given + * featureId or null if there is no such feature. + */ + getFeatureById: function(featureId) { + return this.getFeatureBy('id', featureId); + }, + + /** + * APIMethod: getFeatureByFid + * Given a feature fid, return the feature if it exists in the features array + * + * Parameters: + * featureFid - {String} + * + * Returns: + * {} A feature corresponding to the given + * featureFid or null if there is no such feature. + */ + getFeatureByFid: function(featureFid) { + return this.getFeatureBy('fid', featureFid); + }, + + /** + * APIMethod: getFeaturesByAttribute + * Returns an array of features that have the given attribute key set to the + * given value. Comparison of attribute values takes care of datatypes, e.g. + * the string '1234' is not equal to the number 1234. + * + * Parameters: + * attrName - {String} + * attrValue - {Mixed} + * + * Returns: + * Array({}) An array of features that have the + * passed named attribute set to the given value. + */ + getFeaturesByAttribute: function(attrName, attrValue) { + var i, + feature, + len = this.features.length, + foundFeatures = []; + for(i = 0; i < len; i++) { + feature = this.features[i]; + if(feature && feature.attributes) { + if (feature.attributes[attrName] === attrValue) { + foundFeatures.push(feature); + } + } + } + return foundFeatures; + }, + + /** + * Unselect the selected features + * i.e. clears the featureSelection array + * change the style back + clearSelection: function() { + + var vectorLayer = this.map.vectorLayer; + for (var i = 0; i < this.map.featureSelection.length; i++) { + var featureSelection = this.map.featureSelection[i]; + vectorLayer.drawFeature(featureSelection, vectorLayer.style); + } + this.map.featureSelection = []; + }, + */ + + + /** + * APIMethod: onFeatureInsert + * method called after a feature is inserted. + * Does nothing by default. Override this if you + * need to do something on feature updates. + * + * Parameters: + * feature - {} + */ + onFeatureInsert: function(feature) { + }, + + /** + * APIMethod: preFeatureInsert + * method called before a feature is inserted. + * Does nothing by default. Override this if you + * need to do something when features are first added to the + * layer, but before they are drawn, such as adjust the style. + * + * Parameters: + * feature - {} + */ + preFeatureInsert: function(feature) { + }, + + /** + * APIMethod: getDataExtent + * Calculates the max extent which includes all of the features. + * + * Returns: + * {} or null if the layer has no features with + * geometries. + */ + getDataExtent: function () { + var maxExtent = null; + var features = this.features; + if(features && (features.length > 0)) { + var geometry = null; + for(var i=0, len=features.length; i constructor. + * (code) + * // create a grid with points spaced at 10 map units + * var points = new OpenLayers.Layer.PointGrid({dx: 10, dy: 10}); + * + * // create a grid with different x/y spacing rotated 15 degrees clockwise. + * var points = new OpenLayers.Layer.PointGrid({dx: 5, dy: 10, rotation: 15}); + * (end) + * + * Inherits from: + * - + */ +OpenLayers.Layer.PointGrid = OpenLayers.Class(OpenLayers.Layer.Vector, { + + /** + * APIProperty: dx + * {Number} Point grid spacing in the x-axis direction (map units). + * Read-only. Use the method to modify this value. + */ + dx: null, + + /** + * APIProperty: dy + * {Number} Point grid spacing in the y-axis direction (map units). + * Read-only. Use the method to modify this value. + */ + dy: null, + + /** + * APIProperty: ratio + * {Number} Ratio of the desired grid size to the map viewport size. + * Default is 1.5. Larger ratios mean the grid is recalculated less often + * while panning. The setting has precedence when determining + * grid size. Read-only. Use the method to modify this value. + */ + ratio: 1.5, + + /** + * APIProperty: maxFeatures + * {Number} The maximum number of points to generate in the grid. Default + * is 250. Read-only. Use the method to modify this value. + */ + maxFeatures: 250, + + /** + * APIProperty: rotation + * {Number} Grid rotation (in degrees clockwise from the positive x-axis). + * Default is 0. Read-only. Use the method to modify this + * value. + */ + rotation: 0, + + /** + * APIProperty: origin + * {} Grid origin. The grid lattice will be aligned with + * the origin. If not set at construction, the center of the map's maximum + * extent is used. Read-only. Use the method to modify this + * value. + */ + origin: null, + + /** + * Property: gridBounds + * {} Internally cached grid bounds (with optional + * rotation applied). + */ + gridBounds: null, + + /** + * Constructor: OpenLayers.Layer.PointGrid + * Creates a new point grid layer. + * + * Parameters: + * config - {Object} An object containing all configuration properties for + * the layer. The and properties are required to be set at + * construction. Any other layer properties may be set in this object. + */ + initialize: function(config) { + config = config || {}; + OpenLayers.Layer.Vector.prototype.initialize.apply(this, [config.name, config]); + }, + + /** + * Method: setMap + * The layer has been added to the map. + * + * Parameters: + * map - {} + */ + setMap: function(map) { + OpenLayers.Layer.Vector.prototype.setMap.apply(this, arguments); + map.events.register("moveend", this, this.onMoveEnd); + }, + + /** + * Method: removeMap + * The layer has been removed from the map. + * + * Parameters: + * map - {} + */ + removeMap: function(map) { + map.events.unregister("moveend", this, this.onMoveEnd); + OpenLayers.Layer.Vector.prototype.removeMap.apply(this, arguments); + }, + + /** + * APIMethod: setRatio + * Set the grid property and update the grid. Can only be called + * after the layer has been added to a map with a center/extent. + * + * Parameters: + * ratio - {Number} + */ + setRatio: function(ratio) { + this.ratio = ratio; + this.updateGrid(true); + }, + + /** + * APIMethod: setMaxFeatures + * Set the grid property and update the grid. Can only be + * called after the layer has been added to a map with a center/extent. + * + * Parameters: + * maxFeatures - {Number} + */ + setMaxFeatures: function(maxFeatures) { + this.maxFeatures = maxFeatures; + this.updateGrid(true); + }, + + /** + * APIMethod: setSpacing + * Set the grid and properties and update the grid. If only one + * argument is provided, it will be set as and . Can only be + * called after the layer has been added to a map with a center/extent. + * + * Parameters: + * dx - {Number} + * dy - {Number} + */ + setSpacing: function(dx, dy) { + this.dx = dx; + this.dy = dy || dx; + this.updateGrid(true); + }, + + /** + * APIMethod: setOrigin + * Set the grid property and update the grid. Can only be called + * after the layer has been added to a map with a center/extent. + * + * Parameters: + * origin - {} + */ + setOrigin: function(origin) { + this.origin = origin; + this.updateGrid(true); + }, + + /** + * APIMethod: getOrigin + * Get the grid property. + * + * Returns: + * {} The grid origin. + */ + getOrigin: function() { + if (!this.origin) { + this.origin = this.map.getExtent().getCenterLonLat(); + } + return this.origin; + }, + + /** + * APIMethod: setRotation + * Set the grid property and update the grid. Rotation values + * are in degrees clockwise from the positive x-axis (negative values + * for counter-clockwise rotation). Can only be called after the layer + * has been added to a map with a center/extent. + * + * Parameters: + * rotation - {Number} Degrees clockwise from the positive x-axis. + */ + setRotation: function(rotation) { + this.rotation = rotation; + this.updateGrid(true); + }, + + /** + * Method: onMoveEnd + * Listener for map "moveend" events. + */ + onMoveEnd: function() { + this.updateGrid(); + }, + + /** + * Method: getViewBounds + * Gets the (potentially rotated) view bounds for grid calculations. + * + * Returns: + * {} + */ + getViewBounds: function() { + var bounds = this.map.getExtent(); + if (this.rotation) { + var origin = this.getOrigin(); + var rotationOrigin = new OpenLayers.Geometry.Point(origin.lon, origin.lat); + var rect = bounds.toGeometry(); + rect.rotate(-this.rotation, rotationOrigin); + bounds = rect.getBounds(); + } + return bounds; + }, + + /** + * Method: updateGrid + * Update the grid. + * + * Parameters: + * force - {Boolean} Update the grid even if the previous bounds are still + * valid. + */ + updateGrid: function(force) { + if (force || this.invalidBounds()) { + var viewBounds = this.getViewBounds(); + var origin = this.getOrigin(); + var rotationOrigin = new OpenLayers.Geometry.Point(origin.lon, origin.lat); + var viewBoundsWidth = viewBounds.getWidth(); + var viewBoundsHeight = viewBounds.getHeight(); + var aspectRatio = viewBoundsWidth / viewBoundsHeight; + var maxHeight = Math.sqrt(this.dx * this.dy * this.maxFeatures / aspectRatio); + var maxWidth = maxHeight * aspectRatio; + var gridWidth = Math.min(viewBoundsWidth * this.ratio, maxWidth); + var gridHeight = Math.min(viewBoundsHeight * this.ratio, maxHeight); + var center = viewBounds.getCenterLonLat(); + this.gridBounds = new OpenLayers.Bounds( + center.lon - (gridWidth / 2), + center.lat - (gridHeight / 2), + center.lon + (gridWidth / 2), + center.lat + (gridHeight / 2) + ); + var rows = Math.floor(gridHeight / this.dy); + var cols = Math.floor(gridWidth / this.dx); + var gridLeft = origin.lon + (this.dx * Math.ceil((this.gridBounds.left - origin.lon) / this.dx)); + var gridBottom = origin.lat + (this.dy * Math.ceil((this.gridBounds.bottom - origin.lat) / this.dy)); + var features = new Array(rows * cols); + var x, y, point; + for (var i=0; i + */ +OpenLayers.Handler.MouseWheel = OpenLayers.Class(OpenLayers.Handler, { + /** + * Property: wheelListener + * {function} + */ + wheelListener: null, + + /** + * Property: interval + * {Integer} In order to increase server performance, an interval (in + * milliseconds) can be set to reduce the number of up/down events + * called. If set, a new up/down event will not be set until the + * interval has passed. + * Defaults to 0, meaning no interval. + */ + interval: 0, + + /** + * Property: maxDelta + * {Integer} Maximum delta to collect before breaking from the current + * interval. In cumulative mode, this also limits the maximum delta + * returned from the handler. Default is Number.POSITIVE_INFINITY. + */ + maxDelta: Number.POSITIVE_INFINITY, + + /** + * Property: delta + * {Integer} When interval is set, delta collects the mousewheel z-deltas + * of the events that occur within the interval. + * See also the cumulative option + */ + delta: 0, + + /** + * Property: cumulative + * {Boolean} When interval is set: true to collect all the mousewheel + * z-deltas, false to only record the delta direction (positive or + * negative) + */ + cumulative: true, + + /** + * Constructor: OpenLayers.Handler.MouseWheel + * + * Parameters: + * control - {} + * 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 point geometry. + * options - {Object} + */ + initialize: function(control, callbacks, options) { + OpenLayers.Handler.prototype.initialize.apply(this, arguments); + this.wheelListener = OpenLayers.Function.bindAsEventListener( + this.onWheelEvent, this + ); + }, + + /** + * Method: destroy + */ + destroy: function() { + OpenLayers.Handler.prototype.destroy.apply(this, arguments); + this.wheelListener = null; + }, + + /** + * Mouse ScrollWheel code thanks to http://adomas.org/javascript-mouse-wheel/ + */ + + /** + * Method: onWheelEvent + * Catch the wheel event and handle it xbrowserly + * + * Parameters: + * e - {Event} + */ + onWheelEvent: function(e){ + + // make sure we have a map and check keyboard modifiers + if (!this.map || !this.checkModifiers(e)) { + return; + } + + // Ride up the element's DOM hierarchy to determine if it or any of + // its ancestors was: + // * specifically marked as scrollable (CSS overflow property) + // * one of our layer divs or a div marked as scrollable + // ('olScrollable' CSS class) + // * the map div + // + var overScrollableDiv = false; + var allowScroll = false; + var overMapDiv = false; + + var elem = OpenLayers.Event.element(e); + while((elem != null) && !overMapDiv && !overScrollableDiv) { + + if (!overScrollableDiv) { + try { + var overflow; + if (elem.currentStyle) { + overflow = elem.currentStyle["overflow"]; + } else { + var style = + document.defaultView.getComputedStyle(elem, null); + overflow = style.getPropertyValue("overflow"); + } + overScrollableDiv = ( overflow && + (overflow == "auto") || (overflow == "scroll") ); + } catch(err) { + //sometimes when scrolling in a popup, this causes + // obscure browser error + } + } + + if (!allowScroll) { + allowScroll = OpenLayers.Element.hasClass(elem, 'olScrollable'); + if (!allowScroll) { + for (var i = 0, len = this.map.layers.length; i < len; i++) { + // Are we in the layer div? Note that we have two cases + // here: one is to catch EventPane layers, which have a + // pane above the layer (layer.pane) + var layer = this.map.layers[i]; + if (elem == layer.div || elem == layer.pane) { + allowScroll = true; + break; + } + } + } + } + overMapDiv = (elem == this.map.div); + + elem = elem.parentNode; + } + + // Logic below is the following: + // + // If we are over a scrollable div or not over the map div: + // * do nothing (let the browser handle scrolling) + // + // otherwise + // + // If we are over the layer div or a 'olScrollable' div: + // * zoom/in out + // then + // * kill event (so as not to also scroll the page after zooming) + // + // otherwise + // + // Kill the event (dont scroll the page if we wheel over the + // layerswitcher or the pan/zoom control) + // + if (!overScrollableDiv && overMapDiv) { + if (allowScroll) { + var delta = 0; + + if (e.wheelDelta) { + delta = e.wheelDelta; + if (delta % 160 === 0) { + // opera have steps of 160 instead of 120 + delta = delta * 0.75; + } + delta = delta / 120; + } else if (e.detail) { + // detail in Firefox on OS X is 1/3 of Windows + // so force delta 1 / -1 + delta = - (e.detail / Math.abs(e.detail)); + } + this.delta += delta; + + window.clearTimeout(this._timeoutId); + if(this.interval && Math.abs(this.delta) < this.maxDelta) { + // store e because window.event might change during delay + var evt = OpenLayers.Util.extend({}, e); + this._timeoutId = window.setTimeout( + OpenLayers.Function.bind(function(){ + this.wheelZoom(evt); + }, this), + this.interval + ); + } else { + this.wheelZoom(e); + } + } + OpenLayers.Event.stop(e); + } + }, + + /** + * Method: wheelZoom + * Given the wheel event, we carry out the appropriate zooming in or out, + * based on the 'wheelDelta' or 'detail' property of the event. + * + * Parameters: + * e - {Event} + */ + wheelZoom: function(e) { + var delta = this.delta; + this.delta = 0; + + if (delta) { + e.xy = this.map.events.getMousePosition(e); + if (delta < 0) { + this.callback("down", + [e, this.cumulative ? Math.max(-this.maxDelta, delta) : -1]); + } else { + this.callback("up", + [e, this.cumulative ? Math.min(this.maxDelta, delta) : 1]); + } + } + }, + + /** + * Method: activate + */ + activate: function (evt) { + if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) { + //register mousewheel events specifically on the window and document + var wheelListener = this.wheelListener; + OpenLayers.Event.observe(window, "DOMMouseScroll", wheelListener); + OpenLayers.Event.observe(window, "mousewheel", wheelListener); + OpenLayers.Event.observe(document, "mousewheel", wheelListener); + return true; + } else { + return false; + } + }, + + /** + * Method: deactivate + */ + deactivate: function (evt) { + if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) { + // unregister mousewheel events specifically on the window and document + var wheelListener = this.wheelListener; + OpenLayers.Event.stopObserving(window, "DOMMouseScroll", wheelListener); + OpenLayers.Event.stopObserving(window, "mousewheel", wheelListener); + OpenLayers.Event.stopObserving(document, "mousewheel", wheelListener); + return true; + } else { + return false; + } + }, + + CLASS_NAME: "OpenLayers.Handler.MouseWheel" +}); +/* ====================================================================== + OpenLayers/Symbolizer.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.Symbolizer + * Base class representing a symbolizer used for feature rendering. + */ +OpenLayers.Symbolizer = OpenLayers.Class({ + + + /** + * APIProperty: zIndex + * {Number} The zIndex determines the rendering order for a symbolizer. + * Symbolizers with larger zIndex values are rendered over symbolizers + * with smaller zIndex values. Default is 0. + */ + zIndex: 0, + + /** + * Constructor: OpenLayers.Symbolizer + * Instances of this class are not useful. See one of the subclasses. + * + * Parameters: + * config - {Object} An object containing properties to be set on the + * symbolizer. Any documented symbolizer property can be set at + * construction. + * + * Returns: + * A new symbolizer. + */ + initialize: function(config) { + OpenLayers.Util.extend(this, config); + }, + + /** + * APIMethod: clone + * Create a copy of this symbolizer. + * + * Returns a symbolizer of the same type with the same properties. + */ + clone: function() { + var Type = eval(this.CLASS_NAME); + return new Type(OpenLayers.Util.extend({}, this)); + }, + + CLASS_NAME: "OpenLayers.Symbolizer" + +}); + +/* ====================================================================== + OpenLayers/Symbolizer/Raster.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/Symbolizer.js + */ + +/** + * Class: OpenLayers.Symbolizer.Raster + * A symbolizer used to render raster images. + */ +OpenLayers.Symbolizer.Raster = OpenLayers.Class(OpenLayers.Symbolizer, { + + /** + * Constructor: OpenLayers.Symbolizer.Raster + * Create a symbolizer for rendering rasters. + * + * Parameters: + * config - {Object} An object containing properties to be set on the + * symbolizer. Any documented symbolizer property can be set at + * construction. + * + * Returns: + * A new raster symbolizer. + */ + initialize: function(config) { + OpenLayers.Symbolizer.prototype.initialize.apply(this, arguments); + }, + + CLASS_NAME: "OpenLayers.Symbolizer.Raster" + +}); +/* ====================================================================== + OpenLayers/Rule.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.Rule + * This class represents an SLD Rule, as being used for rule-based SLD styling. + */ +OpenLayers.Rule = OpenLayers.Class({ + + /** + * Property: id + * {String} A unique id for this session. + */ + id: null, + + /** + * APIProperty: name + * {String} name of this rule + */ + name: null, + + /** + * Property: title + * {String} Title of this rule (set if included in SLD) + */ + title: null, + + /** + * Property: description + * {String} Description of this rule (set if abstract is included in SLD) + */ + description: null, + + /** + * Property: context + * {Object} An optional object with properties that the rule should be + * evaluated against. If no context is specified, feature.attributes will + * be used. + */ + context: null, + + /** + * Property: filter + * {} Optional filter for the rule. + */ + filter: null, + + /** + * Property: elseFilter + * {Boolean} Determines whether this rule is only to be applied only if + * no other rules match (ElseFilter according to the SLD specification). + * Default is false. For instances of OpenLayers.Rule, if elseFilter is + * false, the rule will always apply. For subclasses, the else property is + * ignored. + */ + elseFilter: false, + + /** + * Property: symbolizer + * {Object} Symbolizer or hash of symbolizers for this rule. If hash of + * symbolizers, keys are one or more of ["Point", "Line", "Polygon"]. The + * latter if useful if it is required to style e.g. vertices of a line + * with a point symbolizer. Note, however, that this is not implemented + * yet in OpenLayers, but it is the way how symbolizers are defined in + * SLD. + */ + symbolizer: null, + + /** + * Property: symbolizers + * {Array} Collection of symbolizers associated with this rule. If + * provided at construction, the symbolizers array has precedence + * over the deprecated symbolizer property. Note that multiple + * symbolizers are not currently supported by the vector renderers. + * Rules with multiple symbolizers are currently only useful for + * maintaining elements in an SLD document. + */ + symbolizers: null, + + /** + * APIProperty: minScaleDenominator + * {Number} or {String} minimum scale at which to draw the feature. + * In the case of a String, this can be a combination of text and + * propertyNames in the form "literal ${propertyName}" + */ + minScaleDenominator: null, + + /** + * APIProperty: maxScaleDenominator + * {Number} or {String} maximum scale at which to draw the feature. + * In the case of a String, this can be a combination of text and + * propertyNames in the form "literal ${propertyName}" + */ + maxScaleDenominator: null, + + /** + * Constructor: OpenLayers.Rule + * Creates a Rule. + * + * Parameters: + * options - {Object} An optional object with properties to set on the + * rule + * + * Returns: + * {} + */ + initialize: function(options) { + this.symbolizer = {}; + OpenLayers.Util.extend(this, options); + if (this.symbolizers) { + delete this.symbolizer; + } + this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_"); + }, + + /** + * APIMethod: destroy + * nullify references to prevent circular references and memory leaks + */ + destroy: function() { + for (var i in this.symbolizer) { + this.symbolizer[i] = null; + } + this.symbolizer = null; + delete this.symbolizers; + }, + + /** + * APIMethod: evaluate + * evaluates this rule for a specific feature + * + * Parameters: + * feature - {} feature to apply the rule to. + * + * Returns: + * {Boolean} true if the rule applies, false if it does not. + * This rule is the default rule and always returns true. + */ + evaluate: function(feature) { + var context = this.getContext(feature); + var applies = true; + + if (this.minScaleDenominator || this.maxScaleDenominator) { + var scale = feature.layer.map.getScale(); + } + + // check if within minScale/maxScale bounds + if (this.minScaleDenominator) { + applies = scale >= OpenLayers.Style.createLiteral( + this.minScaleDenominator, context); + } + if (applies && this.maxScaleDenominator) { + applies = scale < OpenLayers.Style.createLiteral( + this.maxScaleDenominator, context); + } + + // check if optional filter applies + if(applies && this.filter) { + // feature id filters get the feature, others get the context + if(this.filter.CLASS_NAME == "OpenLayers.Filter.FeatureId") { + applies = this.filter.evaluate(feature); + } else { + applies = this.filter.evaluate(context); + } + } + + return applies; + }, + + /** + * Method: getContext + * Gets the context for evaluating this rule + * + * Paramters: + * feature - {} feature to take the context from if + * none is specified. + */ + getContext: function(feature) { + var context = this.context; + if (!context) { + context = feature.attributes || feature.data; + } + if (typeof this.context == "function") { + context = this.context(feature); + } + return context; + }, + + /** + * APIMethod: clone + * Clones this rule. + * + * Returns: + * {} Clone of this rule. + */ + clone: function() { + var options = OpenLayers.Util.extend({}, this); + if (this.symbolizers) { + // clone symbolizers + var len = this.symbolizers.length; + options.symbolizers = new Array(len); + for (var i=0; i + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Format.SLD = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, { + + /** + * APIProperty: profile + * {String} If provided, use a custom profile. + * + * Currently supported profiles: + * - GeoServer - parses GeoServer vendor specific capabilities for SLD. + */ + profile: null, + + /** + * APIProperty: defaultVersion + * {String} Version number to assume if none found. Default is "1.0.0". + */ + defaultVersion: "1.0.0", + + /** + * APIProperty: stringifyOutput + * {Boolean} If true, write will return a string otherwise a DOMElement. + * Default is true. + */ + stringifyOutput: true, + + /** + * APIProperty: namedLayersAsArray + * {Boolean} Generate a namedLayers array. If false, the namedLayers + * property value will be an object keyed by layer name. Default is + * false. + */ + namedLayersAsArray: false, + + /** + * APIMethod: write + * Write a SLD document given a list of styles. + * + * Parameters: + * sld - {Object} An object representing the SLD. + * options - {Object} Optional configuration object. + * + * Returns: + * {String} An SLD document string. + */ + + /** + * APIMethod: read + * Read and SLD doc and return an object representing the SLD. + * + * Parameters: + * data - {String | DOMElement} Data to read. + * options - {Object} Options for the reader. + * + * Returns: + * {Object} An object representing the SLD. + */ + + CLASS_NAME: "OpenLayers.Format.SLD" +}); +/* ====================================================================== + OpenLayers/Symbolizer/Polygon.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/Symbolizer.js + */ + +/** + * Class: OpenLayers.Symbolizer.Polygon + * A symbolizer used to render line features. + */ +OpenLayers.Symbolizer.Polygon = OpenLayers.Class(OpenLayers.Symbolizer, { + + /** + * APIProperty: strokeColor + * {String} Color for line stroke. This is a RGB hex value (e.g. "#ff0000" + * for red). + * + * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults. + */ + + /** + * APIProperty: strokeOpacity + * {Number} Stroke opacity (0-1). + * + * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults. + */ + + /** + * APIProperty: strokeWidth + * {Number} Pixel stroke width. + * + * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults. + */ + + /** + * APIProperty: strokeLinecap + * {String} Stroke cap type ("butt", "round", or "square"). + * + * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults. + */ + + /** + * Property: strokeDashstyle + * {String} Stroke dash style according to the SLD spec. Note that the + * OpenLayers values for strokeDashstyle ("dot", "dash", "dashdot", + * "longdash", "longdashdot", or "solid") will not work in SLD, but + * most SLD patterns will render correctly in OpenLayers. + * + * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults. + */ + + /** + * APIProperty: fillColor + * {String} RGB hex fill color (e.g. "#ff0000" for red). + * + * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults. + */ + + /** + * APIProperty: fillOpacity + * {Number} Fill opacity (0-1). + * + * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults. + */ + + /** + * Constructor: OpenLayers.Symbolizer.Polygon + * Create a symbolizer for rendering polygons. + * + * Parameters: + * config - {Object} An object containing properties to be set on the + * symbolizer. Any documented symbolizer property can be set at + * construction. + * + * Returns: + * A new polygon symbolizer. + */ + initialize: function(config) { + OpenLayers.Symbolizer.prototype.initialize.apply(this, arguments); + }, + + CLASS_NAME: "OpenLayers.Symbolizer.Polygon" + +}); + +/* ====================================================================== + 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.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/ArgParser.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 + */ + +/** + * Class: OpenLayers.Control.ArgParser + * The ArgParser control adds location bar query string parsing functionality + * to an OpenLayers Map. + * When added to a Map control, on a page load/refresh, the Map will + * automatically take the href string and parse it for lon, lat, zoom, and + * layers information. + * + * Inherits from: + * - + */ +OpenLayers.Control.ArgParser = OpenLayers.Class(OpenLayers.Control, { + + /** + * Property: center + * {} + */ + center: null, + + /** + * Property: zoom + * {int} + */ + zoom: null, + + /** + * Property: layers + * {String} Each character represents the state of the corresponding layer + * on the map. + */ + layers: null, + + /** + * APIProperty: displayProjection + * {} Requires proj4js support. + * Projection used when reading the coordinates from the URL. This will + * reproject the map coordinates from the URL into the map's + * projection. + * + * If you are using this functionality, be aware that any permalink + * which is added to the map will determine the coordinate type which + * is read from the URL, which means you should not add permalinks with + * different displayProjections to the same map. + */ + displayProjection: null, + + /** + * Constructor: OpenLayers.Control.ArgParser + * + * Parameters: + * options - {Object} + */ + + /** + * Method: getParameters + */ + getParameters: function(url) { + url = url || window.location.href; + var parameters = OpenLayers.Util.getParameters(url); + + // If we have an anchor in the url use it to split the url + var index = url.indexOf('#'); + if (index > 0) { + // create an url to parse on the getParameters + url = '?' + url.substring(index + 1, url.length); + + OpenLayers.Util.extend(parameters, + OpenLayers.Util.getParameters(url)); + } + return parameters; + }, + + /** + * Method: setMap + * Set the map property for the control. + * + * Parameters: + * map - {} + */ + setMap: function(map) { + OpenLayers.Control.prototype.setMap.apply(this, arguments); + + //make sure we dont already have an arg parser attached + for(var i=0, len=this.map.controls.length; i + */ +OpenLayers.Control.Permalink = OpenLayers.Class(OpenLayers.Control, { + + /** + * APIProperty: argParserClass + * {Class} The ArgParser control class (not instance) to use with this + * control. + */ + argParserClass: OpenLayers.Control.ArgParser, + + /** + * Property: element + * {DOMElement} + */ + element: null, + + /** + * APIProperty: anchor + * {Boolean} This option changes 3 things: + * the character '#' is used in place of the character '?', + * the window.href is updated if no element is provided. + * When this option is set to true it's not recommend to provide + * a base without provide an element. + */ + anchor: false, + + /** + * APIProperty: base + * {String} + */ + base: '', + + /** + * APIProperty: displayProjection + * {} Requires proj4js support. Projection used + * when creating the coordinates in the link. This will reproject the + * map coordinates into display coordinates. If you are using this + * functionality, the permalink which is last added to the map will + * determine the coordinate type which is read from the URL, which + * means you should not add permalinks with different + * displayProjections to the same map. + */ + displayProjection: null, + + /** + * Constructor: OpenLayers.Control.Permalink + * + * Parameters: + * element - {DOMElement} + * base - {String} + * options - {Object} options to the control. + * + * Or for anchor: + * options - {Object} options to the control. + */ + initialize: function(element, base, options) { + if (element !== null && typeof element == 'object' && !OpenLayers.Util.isElement(element)) { + options = element; + this.base = document.location.href; + OpenLayers.Control.prototype.initialize.apply(this, [options]); + if (this.element != null) { + this.element = OpenLayers.Util.getElement(this.element); + } + } + else { + OpenLayers.Control.prototype.initialize.apply(this, [options]); + this.element = OpenLayers.Util.getElement(element); + this.base = base || document.location.href; + } + }, + + /** + * APIMethod: destroy + */ + destroy: function() { + if (this.element && this.element.parentNode == this.div) { + this.div.removeChild(this.element); + this.element = null; + } + if (this.map) { + this.map.events.unregister('moveend', this, this.updateLink); + } + + OpenLayers.Control.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: setMap + * Set the map property for the control. + * + * Parameters: + * map - {} + */ + setMap: function(map) { + OpenLayers.Control.prototype.setMap.apply(this, arguments); + + //make sure we have an arg parser attached + for(var i=0, len=this.map.controls.length; i} center to encode in the permalink. + * Defaults to the current map center. + * zoom - {Integer} zoom level to encode in the permalink. Defaults to the + * current map zoom level. + * layers - {Array()} layers to encode in the permalink. + * Defaults to the current map layers. + * + * Returns: + * {Object} Hash of parameters that will be url-encoded into the + * permalink. + */ + createParams: function(center, zoom, layers) { + center = center || this.map.getCenter(); + + var params = OpenLayers.Util.getParameters(this.base); + + // If there's still no center, map is not initialized yet. + // Break out of this function, and simply return the params from the + // base link. + if (center) { + + //zoom + params.zoom = zoom || this.map.getZoom(); + + //lon,lat + var lat = center.lat; + var lon = center.lon; + + if (this.displayProjection) { + var mapPosition = OpenLayers.Projection.transform( + { x: lon, y: lat }, + this.map.getProjectionObject(), + this.displayProjection ); + lon = mapPosition.x; + lat = mapPosition.y; + } + params.lat = Math.round(lat*100000)/100000; + params.lon = Math.round(lon*100000)/100000; + + //layers + layers = layers || this.map.layers; + params.layers = ''; + for (var i=0, len=layers.length; i + */ +OpenLayers.Layer.TMS = OpenLayers.Class(OpenLayers.Layer.Grid, { + + /** + * APIProperty: serviceVersion + * {String} Service version for tile requests. Default is "1.0.0". + */ + serviceVersion: "1.0.0", + + /** + * APIProperty: layername + * {String} The identifier for the as advertised by the service. + * For example, if the service advertises a with + * 'href="http://tms.osgeo.org/1.0.0/vmap0"', the property + * would be set to "vmap0". + */ + layername: null, + + /** + * APIProperty: type + * {String} The format extension corresponding to the requested tile image + * type. This is advertised in a element as the + * "extension" attribute. For example, if the service advertises a + * with , + * the property would be set to "jpg". + */ + type: null, + + /** + * APIProperty: isBaseLayer + * {Boolean} Make this layer a base layer. Default is true. Set false to + * use the layer as an overlay. + */ + isBaseLayer: true, + + /** + * 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 bottom-left + * corner of the map's . Default is ``null``. + * + * Example: + * (code) + * var layer = new OpenLayers.Layer.TMS( + * "My Layer", + * "http://tilecache.osgeo.org/wms-c/Basic.py/", + * { + * layername: "basic", + * type: "png", + * // set if different than the bottom left of map.maxExtent + * tileOrigin: new OpenLayers.LonLat(-180, -90) + * } + * ); + * (end) + */ + tileOrigin: null, + + /** + * 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, + + /** + * 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, + + /** + * Constructor: OpenLayers.Layer.TMS + * + * Parameters: + * name - {String} Title to be displayed in a + * url - {String} Service endpoint (without the version number). E.g. + * "http://tms.osgeo.org/". + * options - {Object} Additional properties to be set on the layer. The + * and properties must be set here. + */ + initialize: function(name, url, options) { + var newArguments = []; + newArguments.push(name, url, {}, options); + OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments); + }, + + /** + * APIMethod: clone + * Create a complete copy of this layer. + * + * Parameters: + * obj - {Object} Should only be provided by subclasses that call this + * method. + * + * Returns: + * {} An exact clone of this + */ + clone: function (obj) { + + if (obj == null) { + obj = new OpenLayers.Layer.TMS(this.name, + this.url, + this.getOptions()); + } + + //get all additions from superclasses + obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]); + + // copy/set any non-init, non-simple values here + + 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) { + bounds = this.adjustBounds(bounds); + var res = this.getServerResolution(); + var x = Math.round((bounds.left - this.tileOrigin.lon) / (res * this.tileSize.w)); + var y = Math.round((bounds.bottom - this.tileOrigin.lat) / (res * this.tileSize.h)); + var z = this.getServerZoom(); + var path = this.serviceVersion + "/" + this.layername + "/" + z + "/" + x + "/" + y + "." + this.type; + var url = this.url; + if (OpenLayers.Util.isArray(url)) { + url = this.selectUrl(path, url); + } + return url + path; + }, + + /** + * Method: 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.map.maxExtent.left, + this.map.maxExtent.bottom); + } + }, + + CLASS_NAME: "OpenLayers.Layer.TMS" +}); +/* ====================================================================== + OpenLayers/Format/WCSCapabilities.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 + */ + +/** + * Class: OpenLayers.Format.WCSCapabilities + * Read WCS Capabilities. + * + * Inherits from: + * - + */ +OpenLayers.Format.WCSCapabilities = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, { + + /** + * APIProperty: defaultVersion + * {String} Version number to assume if none found. Default is "1.1.0". + */ + defaultVersion: "1.1.0", + + /** + * Constructor: OpenLayers.Format.WCSCapabilities + * Create a new parser for WCS capabilities. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + + /** + * APIMethod: read + * Read capabilities data from a string, and return a list of coverages. + * + * Parameters: + * data - {String} or {DOMElement} data to read/parse. + * + * Returns: + * {Array} List of named coverages. + */ + + CLASS_NAME: "OpenLayers.Format.WCSCapabilities" + +}); +/* ====================================================================== + OpenLayers/Format/WCSCapabilities/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/WCSCapabilities.js + */ + +/** + * Class: OpenLayers.Format.WCSCapabilities.v1 + * Abstract class not to be instantiated directly. + * + * Inherits from: + * - + */ +OpenLayers.Format.WCSCapabilities.v1 = OpenLayers.Class( + OpenLayers.Format.XML, { + + regExes: { + trimSpace: (/^\s*|\s*$/g), + splitSpace: (/\s+/) + }, + + /** + * Property: defaultPrefix + */ + defaultPrefix: "wcs", + + /** + * APIMethod: read + * Read capabilities data from a string, and return a list of coverages. + * + * Parameters: + * data - {String} or {DOMElement} data to read/parse. + * + * Returns: + * {Array} List of named coverages. + */ + read: function(data) { + if(typeof data == "string") { + data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); + } + var raw = data; + if(data && data.nodeType == 9) { + data = data.documentElement; + } + var capabilities = {}; + this.readNode(data, capabilities); + return capabilities; + }, + + CLASS_NAME: "OpenLayers.Format.WCSCapabilities.v1" + +}); +/* ====================================================================== + OpenLayers/Format/WCSCapabilities/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/WCSCapabilities/v1.js + * @requires OpenLayers/Format/GML/v3.js + */ + +/** + * Class: OpenLayers.Format.WCSCapabilities/v1_0_0 + * Read WCS Capabilities version 1.0.0. + * + * Inherits from: + * - + */ +OpenLayers.Format.WCSCapabilities.v1_0_0 = OpenLayers.Class( + OpenLayers.Format.WCSCapabilities.v1, { + + /** + * Constructor: OpenLayers.Format.WCSCapabilities.v1_0_0 + * Create a new parser for WCS capabilities version 1.0.0. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + + /** + * Property: namespaces + * {Object} Mapping of namespace aliases to namespace URIs. + */ + namespaces: { + wcs: "http://www.opengis.net/wcs", + xlink: "http://www.w3.org/1999/xlink", + xsi: "http://www.w3.org/2001/XMLSchema-instance", + ows: "http://www.opengis.net/ows" + }, + + /** + * Property: errorProperty + * {String} Which property of the returned object to check for in order to + * determine whether or not parsing has failed. In the case that the + * errorProperty is undefined on the returned object, the document will be + * run through an OGCExceptionReport parser. + */ + errorProperty: "service", + + /** + * 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: { + "wcs": { + "WCS_Capabilities": function(node, obj) { + this.readChildNodes(node, obj); + }, + "Service": function(node, obj) { + obj.service = {}; + this.readChildNodes(node, obj.service); + }, + "name": function(node, service) { + service.name = this.getChildValue(node); + }, + "label": function(node, service) { + service.label = this.getChildValue(node); + }, + "keywords": function(node, service) { + service.keywords = []; + this.readChildNodes(node, service.keywords); + }, + "keyword": function(node, keywords) { + // Append the keyword to the keywords list + keywords.push(this.getChildValue(node)); + }, + "responsibleParty": function(node, service) { + service.responsibleParty = {}; + this.readChildNodes(node, service.responsibleParty); + }, + "individualName": function(node, responsibleParty) { + responsibleParty.individualName = this.getChildValue(node); + }, + "organisationName": function(node, responsibleParty) { + responsibleParty.organisationName = this.getChildValue(node); + }, + "positionName": function(node, responsibleParty) { + responsibleParty.positionName = this.getChildValue(node); + }, + "contactInfo": function(node, responsibleParty) { + responsibleParty.contactInfo = {}; + this.readChildNodes(node, responsibleParty.contactInfo); + }, + "phone": function(node, contactInfo) { + contactInfo.phone = {}; + this.readChildNodes(node, contactInfo.phone); + }, + "voice": function(node, phone) { + phone.voice = this.getChildValue(node); + }, + "facsimile": function(node, phone) { + phone.facsimile = this.getChildValue(node); + }, + "address": function(node, contactInfo) { + contactInfo.address = {}; + this.readChildNodes(node, contactInfo.address); + }, + "deliveryPoint": function(node, address) { + address.deliveryPoint = this.getChildValue(node); + }, + "city": function(node, address) { + address.city = this.getChildValue(node); + }, + "postalCode": function(node, address) { + address.postalCode = this.getChildValue(node); + }, + "country": function(node, address) { + address.country = this.getChildValue(node); + }, + "electronicMailAddress": function(node, address) { + address.electronicMailAddress = this.getChildValue(node); + }, + "fees": function(node, service) { + service.fees = this.getChildValue(node); + }, + "accessConstraints": function(node, service) { + service.accessConstraints = this.getChildValue(node); + }, + "ContentMetadata": function(node, obj) { + obj.contentMetadata = []; + this.readChildNodes(node, obj.contentMetadata); + }, + "CoverageOfferingBrief": function(node, contentMetadata) { + var coverageOfferingBrief = {}; + this.readChildNodes(node, coverageOfferingBrief); + contentMetadata.push(coverageOfferingBrief); + }, + "name": function(node, coverageOfferingBrief) { + coverageOfferingBrief.name = this.getChildValue(node); + }, + "label": function(node, coverageOfferingBrief) { + coverageOfferingBrief.label = this.getChildValue(node); + }, + "lonLatEnvelope": function(node, coverageOfferingBrief) { + var nodeList = this.getElementsByTagNameNS(node, "http://www.opengis.net/gml", "pos"); + + // We expect two nodes here, to create the corners of a bounding box + if(nodeList.length == 2) { + var min = {}; + var max = {}; + + OpenLayers.Format.GML.v3.prototype.readers["gml"].pos.apply(this, [nodeList[0], min]); + OpenLayers.Format.GML.v3.prototype.readers["gml"].pos.apply(this, [nodeList[1], max]); + + coverageOfferingBrief.lonLatEnvelope = {}; + coverageOfferingBrief.lonLatEnvelope.srsName = node.getAttribute("srsName"); + coverageOfferingBrief.lonLatEnvelope.min = min.points[0]; + coverageOfferingBrief.lonLatEnvelope.max = max.points[0]; + } + } + } + }, + + CLASS_NAME: "OpenLayers.Format.WCSCapabilities.v1_0_0" + +}); +/* ====================================================================== + 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/Layer/PointTrack.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/Vector.js + */ + +/** + * Class: OpenLayers.Layer.PointTrack + * Vector layer to display ordered point features as a line, creating one + * LineString feature for each pair of two points. + * + * Inherits from: + * - + */ +OpenLayers.Layer.PointTrack = OpenLayers.Class(OpenLayers.Layer.Vector, { + + /** + * APIProperty: dataFrom + * {} or + * {} optional. If the lines + * should get the data/attributes from one of the two points it is + * composed of, which one should it be? + */ + dataFrom: null, + + /** + * APIProperty: styleFrom + * {} or + * {} optional. If the lines + * should get the style from one of the two points it is composed of, + * which one should it be? + */ + styleFrom: null, + + /** + * Constructor: OpenLayers.PointTrack + * Constructor for a new OpenLayers.PointTrack instance. + * + * Parameters: + * name - {String} name of the layer + * options - {Object} Optional object with properties to tag onto the + * instance. + */ + + /** + * APIMethod: addNodes + * Adds point features that will be used to create lines from, using point + * pairs. The first point of a pair will be the source node, the second + * will be the target node. + * + * Parameters: + * pointFeatures - {Array()} + * options - {Object} + * + * Supported options: + * silent - {Boolean} true to suppress (before)feature(s)added events + */ + addNodes: function(pointFeatures, options) { + if (pointFeatures.length < 2) { + throw new Error("At least two point features have to be added to " + + "create a line from"); + } + + var lines = new Array(pointFeatures.length-1); + + var pointFeature, startPoint, endPoint; + for(var i=0, len=pointFeatures.length; i 0) { + var attributes = (this.dataFrom != null) ? + (pointFeatures[i+this.dataFrom].data || + pointFeatures[i+this.dataFrom].attributes) : + null; + var style = (this.styleFrom != null) ? + (pointFeatures[i+this.styleFrom].style) : + null; + var line = new OpenLayers.Geometry.LineString([startPoint, + endPoint]); + + lines[i-1] = new OpenLayers.Feature.Vector(line, attributes, + style); + } + + startPoint = endPoint; + } + + this.addFeatures(lines, options); + }, + + CLASS_NAME: "OpenLayers.Layer.PointTrack" +}); + +/** + * Constant: OpenLayers.Layer.PointTrack.SOURCE_NODE + * {Number} value for and + * + */ +OpenLayers.Layer.PointTrack.SOURCE_NODE = -1; + +/** + * Constant: OpenLayers.Layer.PointTrack.TARGET_NODE + * {Number} value for and + * + */ +OpenLayers.Layer.PointTrack.TARGET_NODE = 0; + +/** + * Constant: OpenLayers.Layer.PointTrack.dataFrom + * {Object} with the following keys - *deprecated* + * - SOURCE_NODE: take data/attributes from the source node of the line + * - TARGET_NODE: take data/attributes from the target node of the line + */ +OpenLayers.Layer.PointTrack.dataFrom = {'SOURCE_NODE': -1, 'TARGET_NODE': 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/Layer/Markers.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.Markers + * + * Inherits from: + * - + */ +OpenLayers.Layer.Markers = OpenLayers.Class(OpenLayers.Layer, { + + /** + * APIProperty: isBaseLayer + * {Boolean} Markers layer is never a base layer. + */ + isBaseLayer: false, + + /** + * APIProperty: markers + * {Array()} internal marker list + */ + markers: null, + + + /** + * Property: drawn + * {Boolean} internal state of drawing. This is a workaround for the fact + * that the map does not call moveTo with a zoomChanged when the map is + * first starting up. This lets us catch the case where we have *never* + * drawn the layer, and draw it even if the zoom hasn't changed. + */ + drawn: false, + + /** + * Constructor: OpenLayers.Layer.Markers + * Create a Markers layer. + * + * Parameters: + * name - {String} + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function(name, options) { + OpenLayers.Layer.prototype.initialize.apply(this, arguments); + this.markers = []; + }, + + /** + * APIMethod: destroy + */ + destroy: function() { + this.clearMarkers(); + this.markers = null; + OpenLayers.Layer.prototype.destroy.apply(this, arguments); + }, + + /** + * APIMethod: setOpacity + * Sets the opacity for all the markers. + * + * Parameters: + * opacity - {Float} + */ + setOpacity: function(opacity) { + if (opacity != this.opacity) { + this.opacity = opacity; + for (var i=0, len=this.markers.length; i} + * zoomChanged - {Boolean} + * dragging - {Boolean} + */ + moveTo:function(bounds, zoomChanged, dragging) { + OpenLayers.Layer.prototype.moveTo.apply(this, arguments); + + if (zoomChanged || !this.drawn) { + for(var i=0, len=this.markers.length; i} + */ + addMarker: function(marker) { + this.markers.push(marker); + + if (this.opacity < 1) { + marker.setOpacity(this.opacity); + } + + if (this.map && this.map.getExtent()) { + marker.map = this.map; + this.drawMarker(marker); + } + }, + + /** + * APIMethod: removeMarker + * + * Parameters: + * marker - {} + */ + removeMarker: function(marker) { + if (this.markers && this.markers.length) { + OpenLayers.Util.removeItem(this.markers, marker); + marker.erase(); + } + }, + + /** + * Method: clearMarkers + * This method removes all markers from a layer. The markers are not + * destroyed by this function, but are removed from the list of markers. + */ + clearMarkers: function() { + if (this.markers != null) { + while(this.markers.length > 0) { + this.removeMarker(this.markers[0]); + } + } + }, + + /** + * Method: drawMarker + * Calculate the pixel location for the marker, create it, and + * add it to the layer's div + * + * Parameters: + * marker - {} + */ + drawMarker: function(marker) { + var px = this.map.getLayerPxFromLonLat(marker.lonlat); + if (px == null) { + marker.display(false); + } else { + if (!marker.isDrawn()) { + var markerImg = marker.draw(px); + this.div.appendChild(markerImg); + } else if(marker.icon) { + marker.icon.moveTo(px); + } + } + }, + + /** + * APIMethod: getDataExtent + * Calculates the max extent which includes all of the markers. + * + * Returns: + * {} + */ + getDataExtent: function () { + var maxExtent = null; + + if ( this.markers && (this.markers.length > 0)) { + var maxExtent = new OpenLayers.Bounds(); + for(var i=0, len=this.markers.length; i. + * + * Inherits from: + * - + */ +OpenLayers.Control.Pan = OpenLayers.Class(OpenLayers.Control.Button, { + + /** + * APIProperty: slideFactor + * {Integer} Number of pixels by which we'll pan the map in any direction + * on clicking the arrow buttons, defaults to 50. If you want to pan + * by some ratio of the map dimensions, use instead. + */ + slideFactor: 50, + + /** + * APIProperty: slideRatio + * {Number} The fraction of map width/height by which we'll pan the map + * on clicking the arrow buttons. Default is null. If set, will + * override . E.g. if slideRatio is .5, then Pan Up will + * pan up half the map height. + */ + slideRatio: null, + + /** + * Property: direction + * {String} in {'North', 'South', 'East', 'West'} + */ + direction: null, + + /** + * Constructor: OpenLayers.Control.Pan + * Control which handles the panning (in any of the cardinal directions) + * of the map by a set px distance. + * + * Parameters: + * direction - {String} The direction this button should pan. + * options - {Object} An optional object whose properties will be used + * to extend the control. + */ + initialize: function(direction, options) { + + this.direction = direction; + this.CLASS_NAME += this.direction; + + OpenLayers.Control.prototype.initialize.apply(this, [options]); + }, + + /** + * Method: trigger + */ + trigger: function(){ + if (this.map) { + var getSlideFactor = OpenLayers.Function.bind(function (dim) { + return this.slideRatio ? + this.map.getSize()[dim] * this.slideRatio : + this.slideFactor; + }, this); + + switch (this.direction) { + case OpenLayers.Control.Pan.NORTH: + this.map.pan(0, -getSlideFactor("h")); + break; + case OpenLayers.Control.Pan.SOUTH: + this.map.pan(0, getSlideFactor("h")); + break; + case OpenLayers.Control.Pan.WEST: + this.map.pan(-getSlideFactor("w"), 0); + break; + case OpenLayers.Control.Pan.EAST: + this.map.pan(getSlideFactor("w"), 0); + break; + } + } + }, + + CLASS_NAME: "OpenLayers.Control.Pan" +}); + +OpenLayers.Control.Pan.NORTH = "North"; +OpenLayers.Control.Pan.SOUTH = "South"; +OpenLayers.Control.Pan.EAST = "East"; +OpenLayers.Control.Pan.WEST = "West"; +/* ====================================================================== + OpenLayers/Format/CSWGetDomain.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.CSWGetDomain + * Default version is 2.0.2. + * + * Returns: + * {} A CSWGetDomain format of the given version. + */ +OpenLayers.Format.CSWGetDomain = function(options) { + options = OpenLayers.Util.applyDefaults( + options, OpenLayers.Format.CSWGetDomain.DEFAULTS + ); + var cls = OpenLayers.Format.CSWGetDomain["v"+options.version.replace(/\./g, "_")]; + if(!cls) { + throw "Unsupported CSWGetDomain version: " + options.version; + } + return new cls(options); +}; + +/** + * Constant: DEFAULTS + * {Object} Default properties for the CSWGetDomain format. + */ +OpenLayers.Format.CSWGetDomain.DEFAULTS = { + "version": "2.0.2" +}; +/* ====================================================================== + OpenLayers/Format/CSWGetDomain/v2_0_2.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/CSWGetDomain.js + */ + +/** + * Class: OpenLayers.Format.CSWGetDomain.v2_0_2 + * A format for creating CSWGetDomain v2.0.2 transactions. + * Create a new instance with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Format.CSWGetDomain.v2_0_2 = 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", + csw: "http://www.opengis.net/cat/csw/2.0.2" + }, + + /** + * Property: defaultPrefix + * {String} The default prefix (used by Format.XML). + */ + defaultPrefix: "csw", + + /** + * Property: version + * {String} CSW version number. + */ + version: "2.0.2", + + /** + * Property: schemaLocation + * {String} http://www.opengis.net/cat/csw/2.0.2 + * http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd + */ + schemaLocation: "http://www.opengis.net/cat/csw/2.0.2 http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd", + + /** + * APIProperty: PropertyName + * {String} Value of the csw:PropertyName element, used when + * writing a GetDomain document. + */ + PropertyName: null, + + /** + * APIProperty: ParameterName + * {String} Value of the csw:ParameterName element, used when + * writing a GetDomain document. + */ + ParameterName: null, + + /** + * Constructor: OpenLayers.Format.CSWGetDomain.v2_0_2 + * A class for parsing and generating CSWGetDomain v2.0.2 transactions. + * + * Parameters: + * options - {Object} Optional object whose properties will be set on the + * instance. + * + * Valid options properties: + * - PropertyName + * - ParameterName + */ + + /** + * APIMethod: read + * Parse the response from a GetDomain request. + */ + 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 obj = {}; + this.readNode(data, obj); + 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: { + "csw": { + "GetDomainResponse": function(node, obj) { + this.readChildNodes(node, obj); + }, + "DomainValues": function(node, obj) { + if (!(OpenLayers.Util.isArray(obj.DomainValues))) { + obj.DomainValues = []; + } + var attrs = node.attributes; + var domainValue = {}; + for(var i=0, len=attrs.length; i constructor. + * + * Inherits from: + * - + */ +OpenLayers.Format.ArcXML.Features = OpenLayers.Class(OpenLayers.Format.XML, { + + /** + * Constructor: OpenLayers.Format.ArcXML.Features + * Create a new parser/writer for ArcXML Features. Create an instance of this class + * to get a set of features from an ArcXML response. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + + /** + * APIMethod: read + * Read data from a string of ArcXML, and return a set of OpenLayers features. + * + * Parameters: + * data - {String} or {DOMElement} data to read/parse. + * + * Returns: + * {Array()} A collection of features. + */ + read: function(data) { + var axl = new OpenLayers.Format.ArcXML(); + var parsed = axl.read(data); + + return parsed.features.feature; + } +}); +/* ====================================================================== + OpenLayers/Control/Snapping.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/Layer/Vector.js + */ + +/** + * Class: OpenLayers.Control.Snapping + * Acts as a snapping agent while editing vector features. + * + * Inherits from: + * - + */ +OpenLayers.Control.Snapping = OpenLayers.Class(OpenLayers.Control, { + + /** + * 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) + * + * Supported event types (in addition to those from ): + * beforesnap - Triggered before a snap occurs. Listeners receive an + * event object with *point*, *x*, *y*, *distance*, *layer*, and + * *snapType* properties. The point property will be original point + * geometry considered for snapping. The x and y properties represent + * coordinates the point will receive. The distance is the distance + * of the snap. The layer is the target layer. The snapType property + * will be one of "node", "vertex", or "edge". Return false to stop + * snapping from occurring. + * snap - Triggered when a snap occurs. Listeners receive an event with + * *point*, *snapType*, *layer*, and *distance* properties. The point + * will be the location snapped to. The snapType will be one of "node", + * "vertex", or "edge". The layer will be the target layer. The + * distance will be the distance of the snap in map units. + * unsnap - Triggered when a vertex is unsnapped. Listeners receive an + * event with a *point* property. + */ + + /** + * CONSTANT: DEFAULTS + * Default target properties. + */ + DEFAULTS: { + tolerance: 10, + node: true, + edge: true, + vertex: true + }, + + /** + * Property: greedy + * {Boolean} Snap to closest feature in first layer with an eligible + * feature. Default is true. + */ + greedy: true, + + /** + * Property: precedence + * {Array} List representing precedence of different snapping types. + * Default is "node", "vertex", "edge". + */ + precedence: ["node", "vertex", "edge"], + + /** + * Property: resolution + * {Float} The map resolution for the previously considered snap. + */ + resolution: null, + + /** + * Property: geoToleranceCache + * {Object} A cache of geo-tolerances. Tolerance values (in map units) are + * calculated when the map resolution changes. + */ + geoToleranceCache: null, + + /** + * Property: layer + * {} The current editable layer. Set at + * construction or after construction with . + */ + layer: null, + + /** + * Property: feature + * {} The current editable feature. + */ + feature: null, + + /** + * Property: point + * {} The currently snapped vertex. + */ + point: null, + + /** + * Constructor: OpenLayers.Control.Snapping + * Creates a new snapping control. A control is constructed with an editable + * layer and a set of configuration objects for target layers. While the + * control is active, dragging vertices while drawing new features or + * modifying existing features on the editable layer will engage + * snapping to features on the target layers. Whether a vertex snaps to + * a feature on a target layer depends on the target layer configuration. + * + * Parameters: + * options - {Object} An object containing all configuration properties for + * the control. + * + * Valid options: + * layer - {} The editable layer. Features from this + * layer that are digitized or modified may have vertices snapped to + * features from any of the target layers. + * targets - {Array(Object | OpenLayers.Layer.Vector)} A list of objects for + * configuring target layers. See valid properties of the target + * objects below. If the items in the targets list are vector layers + * (instead of configuration objects), the defaults from the + * property will apply. The editable layer itself may be a target + * layer, allowing newly created or edited features to be snapped to + * existing features from the same layer. If no targets are provided + * the layer given in the constructor (as ) will become the + * initial target. + * defaults - {Object} An object with default properties to be applied + * to all target objects. + * greedy - {Boolean} Snap to closest feature in first target layer that + * applies. Default is true. If false, all features in all target + * layers will be checked and the closest feature in all target layers + * will be chosen. The greedy property determines if the order of the + * target layers is significant. By default, the order of the target + * layers is significant where layers earlier in the target layer list + * have precedence over layers later in the list. Within a single + * layer, the closest feature is always chosen for snapping. This + * property only determines whether the search for a closer feature + * continues after an eligible feature is found in a target layer. + * + * Valid target properties: + * layer - {} A target layer. Features from this + * layer will be eligible to act as snapping target for the editable + * layer. + * tolerance - {Float} The distance (in pixels) at which snapping may occur. + * Default is 10. + * node - {Boolean} Snap to nodes (first or last point in a geometry) in + * target layer. Default is true. + * nodeTolerance - {Float} Optional distance at which snapping may occur + * for nodes specifically. If none is provided, will be + * used. + * vertex - {Boolean} Snap to vertices in target layer. Default is true. + * vertexTolerance - {Float} Optional distance at which snapping may occur + * for vertices specifically. If none is provided, will be + * used. + * edge - {Boolean} Snap to edges in target layer. Default is true. + * edgeTolerance - {Float} Optional distance at which snapping may occur + * for edges specifically. If none is provided, will be + * used. + * filter - {} Optional filter to evaluate to determine if + * feature is eligible for snapping. If filter evaluates to true for a + * target feature a vertex may be snapped to the feature. + * minResolution - {Number} If a minResolution is provided, snapping to this + * target will only be considered if the map resolution is greater than + * or equal to this value (the minResolution is inclusive). Default is + * no minimum resolution limit. + * maxResolution - {Number} If a maxResolution is provided, snapping to this + * target will only be considered if the map resolution is strictly + * less than this value (the maxResolution is exclusive). Default is + * no maximum resolution limit. + */ + initialize: function(options) { + OpenLayers.Control.prototype.initialize.apply(this, [options]); + this.options = options || {}; // TODO: this could be done by the super + + // set the editable layer if provided + if(this.options.layer) { + this.setLayer(this.options.layer); + } + // configure target layers + var defaults = OpenLayers.Util.extend({}, this.options.defaults); + this.defaults = OpenLayers.Util.applyDefaults(defaults, this.DEFAULTS); + this.setTargets(this.options.targets); + if(this.targets.length === 0 && this.layer) { + this.addTargetLayer(this.layer); + } + + this.geoToleranceCache = {}; + }, + + /** + * APIMethod: setLayer + * Set the editable layer. Call the setLayer method if the editable layer + * changes and the same control should be used on a new editable layer. + * If the control is already active, it will be active after the new + * layer is set. + * + * Parameters: + * layer - {} The new editable layer. + */ + setLayer: function(layer) { + if(this.active) { + this.deactivate(); + this.layer = layer; + this.activate(); + } else { + this.layer = layer; + } + }, + + /** + * Method: setTargets + * Set the targets for the snapping agent. + * + * Parameters: + * targets - {Array} An array of target configs or target layers. + */ + setTargets: function(targets) { + this.targets = []; + if(targets && targets.length) { + var target; + for(var i=0, len=targets.length; i} A target layer. + */ + addTargetLayer: function(layer) { + this.addTarget({layer: layer}); + }, + + /** + * Method: addTarget + * Add a configured target layer. + * + * Parameters: + * target - {Object} A target config. + */ + addTarget: function(target) { + target = OpenLayers.Util.applyDefaults(target, this.defaults); + target.nodeTolerance = target.nodeTolerance || target.tolerance; + target.vertexTolerance = target.vertexTolerance || target.tolerance; + target.edgeTolerance = target.edgeTolerance || target.tolerance; + this.targets.push(target); + }, + + /** + * Method: removeTargetLayer + * Remove a target layer. + * + * Parameters: + * layer - {} The target layer to remove. + */ + removeTargetLayer: function(layer) { + var target; + for(var i=this.targets.length-1; i>=0; --i) { + target = this.targets[i]; + if(target.layer === layer) { + this.removeTarget(target); + } + } + }, + + /** + * Method: removeTarget + * Remove a target. + * + * Parameters: + * target - {Object} A target config. + * + * Returns: + * {Array} The targets array. + */ + removeTarget: function(target) { + return OpenLayers.Util.removeItem(this.targets, target); + }, + + /** + * APIMethod: activate + * Activate the control. Activating the control registers listeners for + * editing related events so that during feature creation and + * modification, moving vertices will trigger snapping. + */ + activate: function() { + var activated = OpenLayers.Control.prototype.activate.call(this); + if(activated) { + if(this.layer && this.layer.events) { + this.layer.events.on({ + sketchstarted: this.onSketchModified, + sketchmodified: this.onSketchModified, + vertexmodified: this.onVertexModified, + scope: this + }); + } + } + return activated; + }, + + /** + * APIMethod: deactivate + * Deactivate the control. Deactivating the control unregisters listeners + * so feature editing may proceed without engaging the snapping agent. + */ + deactivate: function() { + var deactivated = OpenLayers.Control.prototype.deactivate.call(this); + if(deactivated) { + if(this.layer && this.layer.events) { + this.layer.events.un({ + sketchstarted: this.onSketchModified, + sketchmodified: this.onSketchModified, + vertexmodified: this.onVertexModified, + scope: this + }); + } + } + this.feature = null; + this.point = null; + return deactivated; + }, + + /** + * Method: onSketchModified + * Registered as a listener for the sketchmodified event on the editable + * layer. + * + * Parameters: + * event - {Object} The sketch modified event. + */ + onSketchModified: function(event) { + this.feature = event.feature; + this.considerSnapping(event.vertex, event.vertex); + }, + + /** + * Method: onVertexModified + * Registered as a listener for the vertexmodified event on the editable + * layer. + * + * Parameters: + * event - {Object} The vertex modified event. + */ + onVertexModified: function(event) { + this.feature = event.feature; + var loc = this.layer.map.getLonLatFromViewPortPx(event.pixel); + this.considerSnapping( + event.vertex, new OpenLayers.Geometry.Point(loc.lon, loc.lat) + ); + }, + + /** + * Method: considerSnapping + * + * Parameters: + * point - {} The vertex to be snapped (or + * unsnapped). + * loc - {} The location of the mouse in map + * coords. + */ + considerSnapping: function(point, loc) { + var best = { + rank: Number.POSITIVE_INFINITY, + dist: Number.POSITIVE_INFINITY, + x: null, y: null + }; + var snapped = false; + var result, target; + for(var i=0, len=this.targets.length; i} The location of the mouse in map + * coords. + * + * Returns: + * {Object} A result object with rank, dist, x, and y properties. + * Returns null if candidate is not eligible for snapping. + */ + testTarget: function(target, loc) { + var resolution = this.layer.map.getResolution(); + if ("minResolution" in target) { + if (resolution < target.minResolution) { + return null; + } + } + if ("maxResolution" in target) { + if (resolution >= target.maxResolution) { + return null; + } + } + var tolerance = { + node: this.getGeoTolerance(target.nodeTolerance, resolution), + vertex: this.getGeoTolerance(target.vertexTolerance, resolution), + edge: this.getGeoTolerance(target.edgeTolerance, resolution) + }; + // this could be cached if we don't support setting tolerance values directly + var maxTolerance = Math.max( + tolerance.node, tolerance.vertex, tolerance.edge + ); + var result = { + rank: Number.POSITIVE_INFINITY, dist: Number.POSITIVE_INFINITY + }; + var eligible = false; + var features = target.layer.features; + var feature, type, vertices, vertex, closest, dist, found; + var numTypes = this.precedence.length; + var ll = new OpenLayers.LonLat(loc.x, loc.y); + for(var i=0, len=features.length; i when the map resolution + * has not changed. + * + * Parameters: + * tolerance - {Number} A tolerance value in pixels. + * resolution - {Number} Map resolution. + * + * Returns: + * {Number} A tolerance value in map units. + */ + getGeoTolerance: function(tolerance, resolution) { + if(resolution !== this.resolution) { + this.resolution = resolution; + this.geoToleranceCache = {}; + } + var geoTolerance = this.geoToleranceCache[tolerance]; + if(geoTolerance === undefined) { + geoTolerance = tolerance * resolution; + this.geoToleranceCache[tolerance] = geoTolerance; + } + return geoTolerance; + }, + + /** + * Method: destroy + * Clean up the control. + */ + destroy: function() { + if(this.active) { + this.deactivate(); // TODO: this should be handled by the super + } + delete this.layer; + delete this.targets; + OpenLayers.Control.prototype.destroy.call(this); + }, + + CLASS_NAME: "OpenLayers.Control.Snapping" +}); +/* ====================================================================== + OpenLayers/Format/OWSCommon/v1_1_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/OWSCommon/v1.js + */ + +/** + * Class: OpenLayers.Format.OWSCommon.v1_1_0 + * Parser for OWS Common version 1.1.0. + * + * Inherits from: + * - + */ +OpenLayers.Format.OWSCommon.v1_1_0 = OpenLayers.Class(OpenLayers.Format.OWSCommon.v1, { + + /** + * Property: namespaces + * {Object} Mapping of namespace aliases to namespace URIs. + */ + namespaces: { + ows: "http://www.opengis.net/ows/1.1", + xlink: "http://www.w3.org/1999/xlink" + }, + + /** + * 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: { + "ows": OpenLayers.Util.applyDefaults({ + "ExceptionReport": function(node, obj) { + obj.exceptionReport = { + version: node.getAttribute('version'), + language: node.getAttribute('xml:lang'), + exceptions: [] + }; + this.readChildNodes(node, obj.exceptionReport); + }, + "AllowedValues": function(node, parameter) { + parameter.allowedValues = {}; + this.readChildNodes(node, parameter.allowedValues); + }, + "AnyValue": function(node, parameter) { + parameter.anyValue = true; + }, + "DataType": function(node, parameter) { + parameter.dataType = this.getChildValue(node); + }, + "Range": function(node, allowedValues) { + allowedValues.range = {}; + this.readChildNodes(node, allowedValues.range); + }, + "MinimumValue": function(node, range) { + range.minValue = this.getChildValue(node); + }, + "MaximumValue": function(node, range) { + range.maxValue = this.getChildValue(node); + }, + "Identifier": function(node, obj) { + obj.identifier = this.getChildValue(node); + }, + "SupportedCRS": function(node, obj) { + obj.supportedCRS = this.getChildValue(node); + } + }, OpenLayers.Format.OWSCommon.v1.prototype.readers["ows"]) + }, + + /** + * 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: { + "ows": OpenLayers.Util.applyDefaults({ + "Range": function(range) { + var node = this.createElementNSPlus("ows:Range", { + attributes: { + 'ows:rangeClosure': range.closure + } + }); + this.writeNode("ows:MinimumValue", range.minValue, node); + this.writeNode("ows:MaximumValue", range.maxValue, node); + return node; + }, + "MinimumValue": function(minValue) { + var node = this.createElementNSPlus("ows:MinimumValue", { + value: minValue + }); + return node; + }, + "MaximumValue": function(maxValue) { + var node = this.createElementNSPlus("ows:MaximumValue", { + value: maxValue + }); + return node; + }, + "Value": function(value) { + var node = this.createElementNSPlus("ows:Value", { + value: value + }); + return node; + } + }, OpenLayers.Format.OWSCommon.v1.prototype.writers["ows"]) + }, + + CLASS_NAME: "OpenLayers.Format.OWSCommon.v1_1_0" + +}); +/* ====================================================================== + OpenLayers/Format/WCSGetCoverage.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/OWSCommon/v1_1_0.js + */ + +/** + * Class: OpenLayers.Format.WCSGetCoverage version 1.1.0 + * + * Inherits from: + * - + */ +OpenLayers.Format.WCSGetCoverage = OpenLayers.Class(OpenLayers.Format.XML, { + + /** + * Property: namespaces + * {Object} Mapping of namespace aliases to namespace URIs. + */ + namespaces: { + ows: "http://www.opengis.net/ows/1.1", + wcs: "http://www.opengis.net/wcs/1.1", + xlink: "http://www.w3.org/1999/xlink", + xsi: "http://www.w3.org/2001/XMLSchema-instance" + }, + + /** + * Property: regExes + * Compiled regular expressions for manipulating strings. + */ + regExes: { + trimSpace: (/^\s*|\s*$/g), + removeSpace: (/\s*/g), + splitSpace: (/\s+/), + trimComma: (/\s*,\s*/g) + }, + + /** + * Constant: VERSION + * {String} 1.1.2 + */ + VERSION: "1.1.2", + + /** + * Property: schemaLocation + * {String} Schema location + */ + schemaLocation: "http://www.opengis.net/wcs/1.1 http://schemas.opengis.net/wcs/1.1/wcsGetCoverage.xsd", + + /** + * Constructor: OpenLayers.Format.WCSGetCoverage + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + + /** + * Method: write + * + * Parameters: + * options - {Object} Optional object. + * + * Returns: + * {String} A WCS GetCoverage request XML string. + */ + write: function(options) { + var node = this.writeNode("wcs:GetCoverage", options); + this.setAttributeNS( + node, this.namespaces.xsi, + "xsi:schemaLocation", this.schemaLocation + ); + 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: { + "wcs": { + "GetCoverage": function(options) { + var node = this.createElementNSPlus("wcs:GetCoverage", { + attributes: { + version: options.version || this.VERSION, + service: 'WCS' + } + }); + this.writeNode("ows:Identifier", options.identifier, node); + this.writeNode("wcs:DomainSubset", options.domainSubset, node); + this.writeNode("wcs:Output", options.output, node); + return node; + }, + "DomainSubset": function(domainSubset) { + var node = this.createElementNSPlus("wcs:DomainSubset", {}); + this.writeNode("ows:BoundingBox", domainSubset.boundingBox, node); + if (domainSubset.temporalSubset) { + this.writeNode("wcs:TemporalSubset", domainSubset.temporalSubset, node); + } + return node; + }, + "TemporalSubset": function(temporalSubset) { + var node = this.createElementNSPlus("wcs:TemporalSubset", {}); + for (var i=0, len=temporalSubset.timePeriods.length; i + * 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}