summaryrefslogtreecommitdiff
path: root/misc/openlayers/OpenLayers.mobile.debug.js
diff options
context:
space:
mode:
authorChris Schlaeger <chris@linux.com>2014-08-12 21:56:44 +0200
committerChris Schlaeger <chris@linux.com>2014-08-12 21:56:44 +0200
commitea346a785dc1b3f7c156f6fc33da634e1f1a627b (patch)
treeaf67530553d20b6e82ad60fd79593e9c4abf5565 /misc/openlayers/OpenLayers.mobile.debug.js
parent59741cd535c47f25971bf8c32b25da25ceadc6d5 (diff)
downloadpostrunner-ea346a785dc1b3f7c156f6fc33da634e1f1a627b.zip
Adding jquery, flot and openlayers to be included with the GEM.v0.0.4
Diffstat (limited to 'misc/openlayers/OpenLayers.mobile.debug.js')
-rw-r--r--misc/openlayers/OpenLayers.mobile.debug.js41831
1 files changed, 41831 insertions, 0 deletions
diff --git a/misc/openlayers/OpenLayers.mobile.debug.js b/misc/openlayers/OpenLayers.mobile.debug.js
new file mode 100644
index 0000000..5cb37cd
--- /dev/null
+++ b/misc/openlayers/OpenLayers.mobile.debug.js
@@ -0,0 +1,41831 @@
+/*
+
+ OpenLayers.js -- OpenLayers Map Viewer Library
+
+ Copyright (c) 2006-2013 by OpenLayers Contributors
+ Published under the 2-clause BSD license.
+ See http://openlayers.org/dev/license.txt for the full text of the license, and http://openlayers.org/dev/authors.txt for full list of contributors.
+
+ Includes compressed code under the following licenses:
+
+ (For uncompressed versions of the code used, please see the
+ OpenLayers Github repository: <https://github.com/openlayers/openlayers>)
+
+*/
+
+/**
+ * Contains XMLHttpRequest.js <http://code.google.com/p/xmlhttprequest/>
+ * 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<len; i++) {
+ src = s[i].getAttribute('src');
+ if(src) {
+ m = src.match(r);
+ if(m) {
+ l = m[1];
+ break;
+ }
+ }
+ }
+ return (function() { return l; });
+ })(),
+
+ /**
+ * Property: ImgPath
+ * {String} Set this to the path where control images are stored, a path
+ * given here must end with a slash. If set to '' (which is the default)
+ * OpenLayers will use its script location + "img/".
+ *
+ * You will need to set this property when you have a singlefile build of
+ * OpenLayers that either is not named "OpenLayers.js" or if you move
+ * the file in a way such that the image directory cannot be derived from
+ * the script location.
+ *
+ * If your custom OpenLayers build is named "my-custom-ol.js" and the images
+ * of OpenLayers are in a folder "/resources/external/images/ol" a correct
+ * way of including OpenLayers in your HTML would be:
+ *
+ * (code)
+ * <script src="/path/to/my-custom-ol.js" type="text/javascript"></script>
+ * <script type="text/javascript">
+ * // tell OpenLayers where the control images are
+ * // remember the trailing slash
+ * OpenLayers.ImgPath = "/resources/external/images/ol/";
+ * </script>
+ * (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 <link>-tag,
+ * e.g.:
+ *
+ * (code)
+ * <link rel="stylesheet" href="/path/to/default/style.css" type="text/css">
+ * (end code)
+ */
+ ImgPath : ''
+};
+/* ======================================================================
+ OpenLayers/BaseTypes.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+/**
+ * Header: OpenLayers Base Types
+ * OpenLayers custom string, number and function functions are described here.
+ */
+
+/**
+ * Namespace: OpenLayers.String
+ * Contains convenience functions for string manipulation.
+ */
+OpenLayers.String = {
+
+ /**
+ * APIFunction: startsWith
+ * Test whether a string starts with another string.
+ *
+ * Parameters:
+ * str - {String} The string to test.
+ * sub - {String} The substring to look for.
+ *
+ * Returns:
+ * {Boolean} The first string starts with the second.
+ */
+ startsWith: function(str, sub) {
+ return (str.indexOf(sub) == 0);
+ },
+
+ /**
+ * APIFunction: contains
+ * Test whether a string contains another string.
+ *
+ * Parameters:
+ * str - {String} The string to test.
+ * sub - {String} The substring to look for.
+ *
+ * Returns:
+ * {Boolean} The first string contains the second.
+ */
+ contains: function(str, sub) {
+ return (str.indexOf(sub) != -1);
+ },
+
+ /**
+ * APIFunction: trim
+ * Removes leading and trailing whitespace characters from a string.
+ *
+ * Parameters:
+ * str - {String} The (potentially) space padded string. This string is not
+ * modified.
+ *
+ * Returns:
+ * {String} A trimmed version of the string with all leading and
+ * trailing spaces removed.
+ */
+ trim: function(str) {
+ return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
+ },
+
+ /**
+ * APIFunction: camelize
+ * Camel-case a hyphenated string.
+ * Ex. "chicken-head" becomes "chickenHead", and
+ * "-chicken-head" becomes "ChickenHead".
+ *
+ * Parameters:
+ * str - {String} The string to be camelized. The original is not modified.
+ *
+ * Returns:
+ * {String} The string, camelized
+ */
+ camelize: function(str) {
+ var oStringList = str.split('-');
+ var camelizedString = oStringList[0];
+ for (var i=1, len=oStringList.length; i<len; i++) {
+ var s = oStringList[i];
+ camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
+ }
+ return camelizedString;
+ },
+
+ /**
+ * APIFunction: format
+ * Given a string with tokens in the form ${token}, return a string
+ * with tokens replaced with properties from the given context
+ * object. Represent a literal "${" by doubling it, e.g. "${${".
+ *
+ * Parameters:
+ * template - {String} A string with tokens to be replaced. A template
+ * has the form "literal ${token}" where the token will be replaced
+ * by the value of context["token"].
+ * context - {Object} An optional object with properties corresponding
+ * to the tokens in the format string. If no context is sent, the
+ * window object will be used.
+ * args - {Array} Optional arguments to pass to any functions found in
+ * the context. If a context property is a function, the token
+ * will be replaced by the return from the function called with
+ * these arguments.
+ *
+ * Returns:
+ * {String} A string with tokens replaced from the context object.
+ */
+ format: function(template, context, args) {
+ if(!context) {
+ context = window;
+ }
+
+ // Example matching:
+ // str = ${foo.bar}
+ // match = foo.bar
+ var replacer = function(str, match) {
+ var replacement;
+
+ // Loop through all subs. Example: ${a.b.c}
+ // 0 -> 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<len; i++) {
+ if (i in array) {
+ var val = array[i];
+ if (callback.call(caller, val, i, array)) {
+ selected.push(val);
+ }
+ }
+ }
+ }
+ return selected;
+ }
+
+};
+/* ======================================================================
+ 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<l; i++) {
+ o = arguments[i];
+ if(typeof o === "function") {
+ o = o.prototype;
+ }
+ OpenLayers.Util.extend(C.prototype, o);
+ }
+};
+
+/**
+ * APIFunction: extend
+ * Copy all properties of a source object to a destination object. Modifies
+ * the passed in destination object. Any properties on the source object
+ * that are set to undefined will not be (re)set on the destination object.
+ *
+ * Parameters:
+ * destination - {Object} The object that will be modified
+ * source - {Object} The object with properties to be set on the destination
+ *
+ * Returns:
+ * {Object} The destination object.
+ */
+OpenLayers.Util = OpenLayers.Util || {};
+OpenLayers.Util.extend = function(destination, source) {
+ destination = destination || {};
+ if (source) {
+ for (var property in source) {
+ var value = source[property];
+ if (value !== undefined) {
+ destination[property] = value;
+ }
+ }
+
+ /**
+ * 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.
+ */
+
+ /*
+ * 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 sourceIsEvt = typeof window.Event == "function"
+ && source instanceof window.Event;
+
+ if (!sourceIsEvt
+ && source.hasOwnProperty && source.hasOwnProperty("toString")) {
+ destination.toString = source.toString;
+ }
+ }
+ return destination;
+};
+/* ======================================================================
+ OpenLayers/BaseTypes/Bounds.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.Bounds
+ * Instances of this class represent bounding boxes. Data stored as left,
+ * bottom, right, top floats. All values are initialized to null, however,
+ * you should make sure you set them before using the bounds for anything.
+ *
+ * Possible use case:
+ * (code)
+ * bounds = new OpenLayers.Bounds();
+ * bounds.extend(new OpenLayers.LonLat(4,5));
+ * bounds.extend(new OpenLayers.LonLat(5,6));
+ * bounds.toBBOX(); // returns 4,5,5,6
+ * (end)
+ */
+OpenLayers.Bounds = OpenLayers.Class({
+
+ /**
+ * Property: left
+ * {Number} Minimum horizontal coordinate.
+ */
+ left: null,
+
+ /**
+ * Property: bottom
+ * {Number} Minimum vertical coordinate.
+ */
+ bottom: null,
+
+ /**
+ * Property: right
+ * {Number} Maximum horizontal coordinate.
+ */
+ right: null,
+
+ /**
+ * Property: top
+ * {Number} Maximum vertical coordinate.
+ */
+ top: null,
+
+ /**
+ * Property: centerLonLat
+ * {<OpenLayers.LonLat>} A cached center location. This should not be
+ * accessed directly. Use <getCenterLonLat> 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:
+ * {<OpenLayers.Bounds>} 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 - {<OpenLayers.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:
+ * {<OpenLayers.Geometry.Polygon>} 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 <OpenLayers.Size> object of the bounds.
+ *
+ * Returns:
+ * {<OpenLayers.Size>} The size of the bounds.
+ */
+ getSize:function() {
+ return new OpenLayers.Size(this.getWidth(), this.getHeight());
+ },
+
+ /**
+ * APIMethod: getCenterPixel
+ * Returns the <OpenLayers.Pixel> object which represents the center of the
+ * bounds.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} 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 <OpenLayers.LonLat> object which represents the center of the
+ * bounds.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} 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 - {<OpenLayers.Pixel> or <OpenLayers.LonLat>}
+ * Default is center.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} 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:
+ * {<OpenLayers.Bounds>} 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 <OpenLayers.LonLat>,
+ * <OpenLayers.Geometry.Point> or <OpenLayers.Bounds> specified.
+ *
+ * Please note that this function assumes that left < right and
+ * bottom < top.
+ *
+ * Parameters:
+ * object - {<OpenLayers.LonLat>, <OpenLayers.Geometry.Point> or
+ * <OpenLayers.Bounds>} 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 <OpenLayers.LonLat>.
+ *
+ * Parameters:
+ * ll - {<OpenLayers.LonLat>|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 - {<OpenLayers.Bounds>} 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 <OpenLayers.Pixel>.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ * 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 - {<OpenLayers.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 - {<OpenLayers.Bounds>} 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 <OpenLayers.Bounds>.
+ *
+ * bounds - {<OpenLayers.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
+ * <OpenLayers.LonLat> lies.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.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 - {<OpenLayers.Projection>} Source projection.
+ * dest - {<OpenLayers.Projection>} Destination projection.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} 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 - {<OpenLayers.Bounds>}
+ * 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:
+ * {<OpenLayers.Bounds>} 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:
+ * {<OpenLayers.Bounds>} 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:
+ * {<OpenLayers.Bounds>} 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 - {<OpenLayers.Size> or Object} <OpenLayers.Size> or an object with
+ * both 'w' and 'h' properties.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} 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<len; i++) {
+ var element = OpenLayers.Util.getElement(arguments[i]);
+ var display = OpenLayers.Element.visible(element) ? 'none'
+ : '';
+ element.style.display = display;
+ }
+ },
+
+ /**
+ * APIFunction: remove
+ * Remove the specified element from the DOM.
+ *
+ * Parameters:
+ * element - {DOMElement}
+ */
+ remove: function(element) {
+ element = OpenLayers.Util.getElement(element);
+ element.parentNode.removeChild(element);
+ },
+
+ /**
+ * APIFunction: getHeight
+ *
+ * Parameters:
+ * element - {DOMElement}
+ *
+ * Returns:
+ * {Integer} The offset height of the element passed in
+ */
+ getHeight: function(element) {
+ element = OpenLayers.Util.getElement(element);
+ return element.offsetHeight;
+ },
+
+ /**
+ * Function: hasClass
+ * Tests if an element has the given CSS class name.
+ *
+ * Parameters:
+ * element - {DOMElement} A DOM element node.
+ * name - {String} The CSS class name to search for.
+ *
+ * Returns:
+ * {Boolean} The element has the given class name.
+ */
+ hasClass: function(element, name) {
+ var names = element.className;
+ return (!!names && new RegExp("(^|\\s)" + name + "(\\s|$)").test(names));
+ },
+
+ /**
+ * Function: addClass
+ * Add a CSS class name to an element. Safe where element already has
+ * the class name.
+ *
+ * Parameters:
+ * element - {DOMElement} A DOM element node.
+ * name - {String} The CSS class name to add.
+ *
+ * Returns:
+ * {DOMElement} The element.
+ */
+ addClass: function(element, name) {
+ if(!OpenLayers.Element.hasClass(element, name)) {
+ element.className += (element.className ? " " : "") + name;
+ }
+ return element;
+ },
+
+ /**
+ * Function: removeClass
+ * Remove a CSS class name from an element. Safe where element does not
+ * have the class name.
+ *
+ * Parameters:
+ * element - {DOMElement} A DOM element node.
+ * name - {String} The CSS class name to remove.
+ *
+ * Returns:
+ * {DOMElement} The element.
+ */
+ removeClass: function(element, name) {
+ var names = element.className;
+ if(names) {
+ element.className = OpenLayers.String.trim(
+ names.replace(
+ new RegExp("(^|\\s+)" + name + "(\\s+|$)"), " "
+ )
+ );
+ }
+ return element;
+ },
+
+ /**
+ * Function: toggleClass
+ * Remove a CSS class name from an element if it exists. Add the class name
+ * if it doesn't exist.
+ *
+ * Parameters:
+ * element - {DOMElement} A DOM element node.
+ * name - {String} The CSS class name to toggle.
+ *
+ * Returns:
+ * {DOMElement} The element.
+ */
+ toggleClass: function(element, name) {
+ if(OpenLayers.Element.hasClass(element, name)) {
+ OpenLayers.Element.removeClass(element, name);
+ } else {
+ OpenLayers.Element.addClass(element, name);
+ }
+ return element;
+ },
+
+ /**
+ * APIFunction: getStyle
+ *
+ * Parameters:
+ * element - {DOMElement}
+ * style - {?}
+ *
+ * Returns:
+ * {?}
+ */
+ getStyle: function(element, style) {
+ element = OpenLayers.Util.getElement(element);
+
+ var value = null;
+ if (element && element.style) {
+ value = element.style[OpenLayers.String.camelize(style)];
+ if (!value) {
+ if (document.defaultView &&
+ document.defaultView.getComputedStyle) {
+
+ var css = document.defaultView.getComputedStyle(element, null);
+ value = css ? css.getPropertyValue(style) : null;
+ } else if (element.currentStyle) {
+ value = element.currentStyle[OpenLayers.String.camelize(style)];
+ }
+ }
+
+ var positions = ['left', 'top', 'right', 'bottom'];
+ if (window.opera &&
+ (OpenLayers.Util.indexOf(positions,style) != -1) &&
+ (OpenLayers.Element.getStyle(element, 'position') == 'static')) {
+ value = 'auto';
+ }
+ }
+
+ return value == 'auto' ? null : value;
+ }
+
+};
+/* ======================================================================
+ OpenLayers/BaseTypes/LonLat.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.LonLat
+ * This class represents a longitude and latitude pair
+ */
+OpenLayers.LonLat = OpenLayers.Class({
+
+ /**
+ * APIProperty: lon
+ * {Float} The x-axis coodinate in map units
+ */
+ lon: 0.0,
+
+ /**
+ * APIProperty: lat
+ * {Float} The y-axis coordinate in map units
+ */
+ lat: 0.0,
+
+ /**
+ * Constructor: OpenLayers.LonLat
+ * Create a new map location. Coordinates can be passed either as two
+ * arguments, or as a single argument.
+ *
+ * Parameters (two arguments):
+ * lon - {Number} The x-axis coordinate in map units. If your map is in
+ * a geographic projection, this will be the Longitude. Otherwise,
+ * it will be the x coordinate of the map location in your map units.
+ * lat - {Number} The y-axis coordinate in map units. If your map is in
+ * a geographic projection, this will be the Latitude. Otherwise,
+ * it will be the y coordinate of the map location in your map units.
+ *
+ * Parameters (single argument):
+ * location - {Array(Float)} [lon, lat]
+ */
+ initialize: function(lon, lat) {
+ if (OpenLayers.Util.isArray(lon)) {
+ lat = lon[1];
+ lon = lon[0];
+ }
+ this.lon = OpenLayers.Util.toFloat(lon);
+ this.lat = OpenLayers.Util.toFloat(lat);
+ },
+
+ /**
+ * Method: toString
+ * Return a readable string version of the lonlat
+ *
+ * Returns:
+ * {String} String representation of OpenLayers.LonLat object.
+ * (e.g. <i>"lon=5,lat=42"</i>)
+ */
+ toString:function() {
+ return ("lon=" + this.lon + ",lat=" + this.lat);
+ },
+
+ /**
+ * APIMethod: toShortString
+ *
+ * Returns:
+ * {String} Shortened String representation of OpenLayers.LonLat object.
+ * (e.g. <i>"5, 42"</i>)
+ */
+ toShortString:function() {
+ return (this.lon + ", " + this.lat);
+ },
+
+ /**
+ * APIMethod: clone
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} 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:
+ * {<OpenLayers.LonLat>} 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 - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {Boolean} Boolean value indicating whether the passed-in
+ * <OpenLayers.LonLat> 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 - {<OpenLayers.Projection>} Source projection.
+ * dest - {<OpenLayers.Projection>} Destination projection.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} 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 - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} 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 <OpenLayers.LonLat> from a
+ * parameter string
+ *
+ * Parameters:
+ * str - {String} Comma-separated Lon,Lat coordinate string.
+ * (e.g. <i>"5,40"</i>)
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} New <OpenLayers.LonLat> 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 <OpenLayers.LonLat> 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:
+ * {<OpenLayers.LonLat>} New <OpenLayers.LonLat> 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:
+ * {<OpenLayers.Pixel>} 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 - {<OpenLayers.Pixel>|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 - {<OpenLayers.Pixel>}
+ *
+ * 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:
+ * {<OpenLayers.Pixel>} 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 - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} 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. <i>"w=55,h=66"</i>)
+ */
+ toString:function() {
+ return ("w=" + this.w + ",h=" + this.h);
+ },
+
+ /**
+ * APIMethod: clone
+ * Create a clone of this size object
+ *
+ * Returns:
+ * {<OpenLayers.Size>} 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 - {<OpenLayers.Size>|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<len; ++i) {
+ if(scripts[i].src.indexOf("firebug.js") != -1) {
+ if(console) {
+ OpenLayers.Util.extend(OpenLayers.Console, console);
+ break;
+ }
+ }
+ }
+})();
+/* ======================================================================
+ OpenLayers/Lang.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/Console.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang
+ * Internationalization namespace. Contains dictionaries in various languages
+ * and methods to set and get the current language.
+ */
+OpenLayers.Lang = {
+
+ /**
+ * Property: code
+ * {String} Current language code to use in OpenLayers. Use the
+ * <setCode> method to set this value and the <getCode> 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 <OpenLayers.Lang.translate> 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 <OpenLayers.Lang>
+ * dictionary exists for the code, the <OpenLayers.String.defaultLang>
+ * 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 <getCode> will be used to determine the appropriate
+ * dictionary. Dictionaries are stored in <OpenLayers.Lang>.
+ *
+ * Parameters:
+ * key - {String} The key for an i18n string value in the dictionary.
+ * context - {Object} Optional context to be used with
+ * <OpenLayers.String.format>.
+ *
+ * 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 <OpenLayers.Lang.translate>. Looks up a key from a dictionary
+ * based on the current language string. The value of
+ * <OpenLayers.Lang.getCode> will be used to determine the appropriate
+ * dictionary. Dictionaries are stored in <OpenLayers.Lang>.
+ *
+ * Parameters:
+ * key - {String} The key for an i18n string value in the dictionary.
+ * context - {Object} Optional context to be used with
+ * <OpenLayers.String.format>.
+ *
+ * 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<len; i++) {
+ var element = arguments[i];
+ if (typeof element == 'string') {
+ element = document.getElementById(element);
+ }
+ if (arguments.length == 1) {
+ return element;
+ }
+ elements.push(element);
+ }
+ return elements;
+};
+
+/**
+ * Function: isElement
+ * A cross-browser implementation of "e instanceof Element".
+ *
+ * Parameters:
+ * o - {Object} The object to test.
+ *
+ * Returns:
+ * {Boolean}
+ */
+OpenLayers.Util.isElement = function(o) {
+ return !!(o && o.nodeType === 1);
+};
+
+/**
+ * Function: isArray
+ * Tests that the provided object is an array.
+ * This test handles the cross-IFRAME case not caught
+ * by "a instanceof Array" and should be used instead.
+ *
+ * Parameters:
+ * a - {Object} the object test.
+ *
+ * Returns:
+ * {Boolean} true if the object is an array.
+ */
+OpenLayers.Util.isArray = function(a) {
+ return (Object.prototype.toString.call(a) === '[object Array]');
+};
+
+/**
+ * Function: removeItem
+ * Remove an object from an array. Iterates through the array
+ * to find the item, then removes it.
+ *
+ * Parameters:
+ * array - {Array}
+ * item - {Object}
+ *
+ * Returns:
+ * {Array} A reference to the array
+ */
+OpenLayers.Util.removeItem = function(array, item) {
+ for(var i = array.length - 1; 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 - {<OpenLayers.Pixel>|Object} The element left and top position,
+ * OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|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 - {<OpenLayers.Pixel>|Object} The element left and top position,
+ * OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|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 - {<OpenLayers.Pixel>|Object} The element left and top position,
+ * OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|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 - {<OpenLayers.Pixel>|Object} OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|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 - {<OpenLayers.Pixel>|Object} OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|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. <i>"key1=value1&key2=value2&key3=value3"</i>)
+ * 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<len; itemIndex++) {
+ item = value[itemIndex];
+ encodedItemArray.push(encodeURIComponent(
+ (item === null || item === undefined) ? "" : item)
+ );
+ }
+ encodedValue = encodedItemArray.join(",");
+ }
+ else {
+ /* value is a string; simply encode */
+ encodedValue = encodeURIComponent(value);
+ }
+ paramsArray.push(encodeURIComponent(key) + "=" + encodedValue);
+ }
+ }
+
+ return paramsArray.join("&");
+};
+
+/**
+ * Function: urlAppend
+ * Appends a parameter string to a url. This function includes the logic for
+ * using the appropriate character (none, & or ?) to append to the url before
+ * appending the param string.
+ *
+ * Parameters:
+ * url - {String} The url to append to
+ * paramStr - {String} The param string to append
+ *
+ * Returns:
+ * {String} The new url
+ */
+OpenLayers.Util.urlAppend = function(url, paramStr) {
+ var newUrl = url;
+ if(paramStr) {
+ var parts = (url + " ").split(/[?&]/);
+ newUrl += (parts.pop() === " " ?
+ paramStr :
+ parts.length ? "&" + paramStr : "?" + paramStr);
+ }
+ return newUrl;
+};
+
+/**
+ * Function: getImagesLocation
+ *
+ * Returns:
+ * {String} The fully formatted image location string
+ */
+OpenLayers.Util.getImagesLocation = function() {
+ return OpenLayers.ImgPath || (OpenLayers._getScriptLocation() + "img/");
+};
+
+/**
+ * Function: getImageLocation
+ *
+ * Returns:
+ * {String} The fully formatted location string for a specified image
+ */
+OpenLayers.Util.getImageLocation = function(image) {
+ return OpenLayers.Util.getImagesLocation() + image;
+};
+
+
+/**
+ * Function: Try
+ * Execute functions until one of them doesn't throw an error.
+ * Capitalized because "try" is a reserved word in JavaScript.
+ * Taken directly from OpenLayers.Util.Try()
+ *
+ * Parameters:
+ * [*] - {Function} Any number of parameters may be passed to Try()
+ * It will attempt to execute each of them until one of them
+ * successfully executes.
+ * If none executes successfully, returns null.
+ *
+ * Returns:
+ * {*} The value returned by the first successfully executed function.
+ */
+OpenLayers.Util.Try = function() {
+ var returnValue = null;
+
+ for (var i=0, len=arguments.length; i<len; i++) {
+ var lambda = arguments[i];
+ try {
+ returnValue = lambda();
+ break;
+ } catch (e) {}
+ }
+
+ return returnValue;
+};
+
+/**
+ * Function: getXmlNodeValue
+ *
+ * Parameters:
+ * node - {XMLNode}
+ *
+ * Returns:
+ * {String} The text value of the given node, without breaking in firefox or IE
+ */
+OpenLayers.Util.getXmlNodeValue = function(node) {
+ var val = null;
+ OpenLayers.Util.Try(
+ function() {
+ val = node.text;
+ if (!val) {
+ val = node.textContent;
+ }
+ if (!val) {
+ val = node.firstChild.nodeValue;
+ }
+ },
+ function() {
+ val = node.textContent;
+ });
+ return val;
+};
+
+/**
+ * Function: mouseLeft
+ *
+ * Parameters:
+ * evt - {Event}
+ * div - {HTMLDivElement}
+ *
+ * Returns:
+ * {Boolean}
+ */
+OpenLayers.Util.mouseLeft = function (evt, div) {
+ // start with the element to which the mouse has moved
+ var target = (evt.relatedTarget) ? evt.relatedTarget : evt.toElement;
+ // walk up the DOM tree.
+ while (target != div && target != null) {
+ target = target.parentNode;
+ }
+ // if the target we stop at isn't the div, then we've left the div.
+ return (target != div);
+};
+
+/**
+ * Property: precision
+ * {Number} The number of significant digits to retain to avoid
+ * floating point precision errors.
+ *
+ * We use 14 as a "safe" default because, although IEEE 754 double floats
+ * (standard on most modern operating systems) support up to about 16
+ * significant digits, 14 significant digits are sufficient to represent
+ * sub-millimeter accuracy in any coordinate system that anyone is likely to
+ * use with OpenLayers.
+ *
+ * If DEFAULT_PRECISION is set to 0, the original non-truncating behavior
+ * of OpenLayers <2.8 is preserved. Be aware that this will cause problems
+ * with certain projections, e.g. spherical Mercator.
+ *
+ */
+OpenLayers.Util.DEFAULT_PRECISION = 14;
+
+/**
+ * Function: toFloat
+ * Convenience method to cast an object to a Number, rounded to the
+ * desired floating point precision.
+ *
+ * Parameters:
+ * number - {Number} The number to cast and round.
+ * precision - {Number} An integer suitable for use with
+ * Number.toPrecision(). Defaults to OpenLayers.Util.DEFAULT_PRECISION.
+ * If set to 0, no rounding is performed.
+ *
+ * Returns:
+ * {Number} The cast, rounded number.
+ */
+OpenLayers.Util.toFloat = function (number, precision) {
+ if (precision == null) {
+ precision = OpenLayers.Util.DEFAULT_PRECISION;
+ }
+ if (typeof number !== "number") {
+ number = parseFloat(number);
+ }
+ return precision === 0 ? number :
+ parseFloat(number.toPrecision(precision));
+};
+
+/**
+ * Function: rad
+ *
+ * Parameters:
+ * x - {Float}
+ *
+ * Returns:
+ * {Float}
+ */
+OpenLayers.Util.rad = function(x) {return x*Math.PI/180;};
+
+/**
+ * Function: deg
+ *
+ * Parameters:
+ * x - {Float}
+ *
+ * Returns:
+ * {Float}
+ */
+OpenLayers.Util.deg = function(x) {return x*180/Math.PI;};
+
+/**
+ * Property: VincentyConstants
+ * {Object} Constants for Vincenty functions.
+ */
+OpenLayers.Util.VincentyConstants = {
+ a: 6378137,
+ b: 6356752.3142,
+ f: 1/298.257223563
+};
+
+/**
+ * APIFunction: distVincenty
+ * Given two objects representing points with geographic coordinates, this
+ * calculates the distance between those points on the surface of an
+ * ellipsoid.
+ *
+ * Parameters:
+ * p1 - {<OpenLayers.LonLat>} (or any object with both .lat, .lon properties)
+ * p2 - {<OpenLayers.LonLat>} (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 - {<OpenLayers.LonLat>} (or any object with both .lat, .lon
+ * properties) The start point.
+ * brng - {Float} The bearing (degrees).
+ * dist - {Float} The ground distance (meters).
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} 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<len; ++i) {
+ var keyValue = pairs[i].split('=');
+ if (keyValue[0]) {
+
+ var key = keyValue[0];
+ try {
+ key = decodeURIComponent(key);
+ } catch (err) {
+ key = unescape(key);
+ }
+
+ // being liberal by replacing "+" with " "
+ var value = (keyValue[1] || '').replace(/\+/g, " ");
+
+ try {
+ value = decodeURIComponent(value);
+ } catch (err) {
+ value = unescape(value);
+ }
+
+ // follow OGC convention of comma delimited values
+ if (options.splitArgs !== false) {
+ value = value.split(",");
+ }
+
+ //if there's only one value, do not return as array
+ if (value.length == 1) {
+ value = value[0];
+ }
+
+ parameters[key] = value;
+ }
+ }
+ return parameters;
+};
+
+/**
+ * Property: lastSeqID
+ * {Integer} The ever-incrementing count variable.
+ * Used for generating unique ids.
+ */
+OpenLayers.Util.lastSeqID = 0;
+
+/**
+ * Function: createUniqueID
+ * Create a unique identifier for this session. Each time this function
+ * is called, a counter is incremented. The return will be the optional
+ * prefix (defaults to "id_") appended with the counter value.
+ *
+ * Parameters:
+ * prefix - {String} Optional string to prefix unique id. Default is "id_".
+ * Note that dots (".") in the prefix will be replaced with underscore ("_").
+ *
+ * Returns:
+ * {String} A unique id string, built on the passed in prefix.
+ */
+OpenLayers.Util.createUniqueID = function(prefix) {
+ if (prefix == null) {
+ prefix = "id_";
+ } else {
+ prefix = prefix.replace(OpenLayers.Util.dotless, "_");
+ }
+ OpenLayers.Util.lastSeqID += 1;
+ return prefix + OpenLayers.Util.lastSeqID;
+};
+
+/**
+ * Constant: INCHES_PER_UNIT
+ * {Object} Constant inches per unit -- borrowed from MapServer mapscale.c
+ * derivation of nautical miles from http://en.wikipedia.org/wiki/Nautical_mile
+ * Includes the full set of units supported by CS-MAP (http://trac.osgeo.org/csmap/)
+ * and PROJ.4 (http://trac.osgeo.org/proj/)
+ * The hardcoded table is maintain in a CS-MAP source code module named CSdataU.c
+ * The hardcoded table of PROJ.4 units are in pj_units.c.
+ */
+OpenLayers.INCHES_PER_UNIT = {
+ 'inches': 1.0,
+ 'ft': 12.0,
+ 'mi': 63360.0,
+ 'm': 39.37,
+ 'km': 39370,
+ 'dd': 4374754,
+ 'yd': 36
+};
+OpenLayers.INCHES_PER_UNIT["in"]= OpenLayers.INCHES_PER_UNIT.inches;
+OpenLayers.INCHES_PER_UNIT["degrees"] = OpenLayers.INCHES_PER_UNIT.dd;
+OpenLayers.INCHES_PER_UNIT["nmi"] = 1852 * OpenLayers.INCHES_PER_UNIT.m;
+
+// Units from CS-Map
+OpenLayers.METERS_PER_INCH = 0.02540005080010160020;
+OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT, {
+ "Inch": OpenLayers.INCHES_PER_UNIT.inches,
+ "Meter": 1.0 / OpenLayers.METERS_PER_INCH, //EPSG:9001
+ "Foot": 0.30480060960121920243 / OpenLayers.METERS_PER_INCH, //EPSG:9003
+ "IFoot": 0.30480000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9002
+ "ClarkeFoot": 0.3047972651151 / OpenLayers.METERS_PER_INCH, //EPSG:9005
+ "SearsFoot": 0.30479947153867624624 / OpenLayers.METERS_PER_INCH, //EPSG:9041
+ "GoldCoastFoot": 0.30479971018150881758 / OpenLayers.METERS_PER_INCH, //EPSG:9094
+ "IInch": 0.02540000000000000000 / OpenLayers.METERS_PER_INCH,
+ "MicroInch": 0.00002540000000000000 / OpenLayers.METERS_PER_INCH,
+ "Mil": 0.00000002540000000000 / OpenLayers.METERS_PER_INCH,
+ "Centimeter": 0.01000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Kilometer": 1000.00000000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9036
+ "Yard": 0.91440182880365760731 / OpenLayers.METERS_PER_INCH,
+ "SearsYard": 0.914398414616029 / OpenLayers.METERS_PER_INCH, //EPSG:9040
+ "IndianYard": 0.91439853074444079983 / OpenLayers.METERS_PER_INCH, //EPSG:9084
+ "IndianYd37": 0.91439523 / OpenLayers.METERS_PER_INCH, //EPSG:9085
+ "IndianYd62": 0.9143988 / OpenLayers.METERS_PER_INCH, //EPSG:9086
+ "IndianYd75": 0.9143985 / OpenLayers.METERS_PER_INCH, //EPSG:9087
+ "IndianFoot": 0.30479951 / OpenLayers.METERS_PER_INCH, //EPSG:9080
+ "IndianFt37": 0.30479841 / OpenLayers.METERS_PER_INCH, //EPSG:9081
+ "IndianFt62": 0.3047996 / OpenLayers.METERS_PER_INCH, //EPSG:9082
+ "IndianFt75": 0.3047995 / OpenLayers.METERS_PER_INCH, //EPSG:9083
+ "Mile": 1609.34721869443738887477 / OpenLayers.METERS_PER_INCH,
+ "IYard": 0.91440000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9096
+ "IMile": 1609.34400000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9093
+ "NautM": 1852.00000000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9030
+ "Lat-66": 110943.316488932731 / OpenLayers.METERS_PER_INCH,
+ "Lat-83": 110946.25736872234125 / OpenLayers.METERS_PER_INCH,
+ "Decimeter": 0.10000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Millimeter": 0.00100000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Dekameter": 10.00000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Decameter": 10.00000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Hectometer": 100.00000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "GermanMeter": 1.0000135965 / OpenLayers.METERS_PER_INCH, //EPSG:9031
+ "CaGrid": 0.999738 / OpenLayers.METERS_PER_INCH,
+ "ClarkeChain": 20.1166194976 / OpenLayers.METERS_PER_INCH, //EPSG:9038
+ "GunterChain": 20.11684023368047 / OpenLayers.METERS_PER_INCH, //EPSG:9033
+ "BenoitChain": 20.116782494375872 / OpenLayers.METERS_PER_INCH, //EPSG:9062
+ "SearsChain": 20.11676512155 / OpenLayers.METERS_PER_INCH, //EPSG:9042
+ "ClarkeLink": 0.201166194976 / OpenLayers.METERS_PER_INCH, //EPSG:9039
+ "GunterLink": 0.2011684023368047 / OpenLayers.METERS_PER_INCH, //EPSG:9034
+ "BenoitLink": 0.20116782494375872 / OpenLayers.METERS_PER_INCH, //EPSG:9063
+ "SearsLink": 0.2011676512155 / OpenLayers.METERS_PER_INCH, //EPSG:9043
+ "Rod": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
+ "IntnlChain": 20.1168 / OpenLayers.METERS_PER_INCH, //EPSG:9097
+ "IntnlLink": 0.201168 / OpenLayers.METERS_PER_INCH, //EPSG:9098
+ "Perch": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
+ "Pole": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
+ "Furlong": 201.1684023368046 / OpenLayers.METERS_PER_INCH,
+ "Rood": 3.778266898 / OpenLayers.METERS_PER_INCH,
+ "CapeFoot": 0.3047972615 / OpenLayers.METERS_PER_INCH,
+ "Brealey": 375.00000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "ModAmFt": 0.304812252984505969011938 / OpenLayers.METERS_PER_INCH,
+ "Fathom": 1.8288 / OpenLayers.METERS_PER_INCH,
+ "NautM-UK": 1853.184 / OpenLayers.METERS_PER_INCH,
+ "50kilometers": 50000.0 / OpenLayers.METERS_PER_INCH,
+ "150kilometers": 150000.0 / OpenLayers.METERS_PER_INCH
+});
+
+//unit abbreviations supported by PROJ.4
+OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT, {
+ "mm": OpenLayers.INCHES_PER_UNIT["Meter"] / 1000.0,
+ "cm": OpenLayers.INCHES_PER_UNIT["Meter"] / 100.0,
+ "dm": OpenLayers.INCHES_PER_UNIT["Meter"] * 100.0,
+ "km": OpenLayers.INCHES_PER_UNIT["Meter"] * 1000.0,
+ "kmi": OpenLayers.INCHES_PER_UNIT["nmi"], //International Nautical Mile
+ "fath": OpenLayers.INCHES_PER_UNIT["Fathom"], //International Fathom
+ "ch": OpenLayers.INCHES_PER_UNIT["IntnlChain"], //International Chain
+ "link": OpenLayers.INCHES_PER_UNIT["IntnlLink"], //International Link
+ "us-in": OpenLayers.INCHES_PER_UNIT["inches"], //U.S. Surveyor's Inch
+ "us-ft": OpenLayers.INCHES_PER_UNIT["Foot"], //U.S. Surveyor's Foot
+ "us-yd": OpenLayers.INCHES_PER_UNIT["Yard"], //U.S. Surveyor's Yard
+ "us-ch": OpenLayers.INCHES_PER_UNIT["GunterChain"], //U.S. Surveyor's Chain
+ "us-mi": OpenLayers.INCHES_PER_UNIT["Mile"], //U.S. Surveyor's Statute Mile
+ "ind-yd": OpenLayers.INCHES_PER_UNIT["IndianYd37"], //Indian Yard
+ "ind-ft": OpenLayers.INCHES_PER_UNIT["IndianFt37"], //Indian Foot
+ "ind-ch": 20.11669506 / OpenLayers.METERS_PER_INCH //Indian Chain
+});
+
+/**
+ * Constant: DOTS_PER_INCH
+ * {Integer} 72 (A sensible default)
+ */
+OpenLayers.DOTS_PER_INCH = 72;
+
+/**
+ * Function: normalizeScale
+ *
+ * Parameters:
+ * scale - {float}
+ *
+ * Returns:
+ * {Float} A normalized scale value, in 1 / X format.
+ * This means that if a value less than one ( already 1/x) is passed
+ * in, it just returns scale directly. Otherwise, it returns
+ * 1 / scale
+ */
+OpenLayers.Util.normalizeScale = function (scale) {
+ var normScale = (scale > 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 - {<OpenLayers.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.Size>}
+ */
+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<l; i++) {
+ if (!content.childNodes[i].style) continue;
+ content.childNodes[i].style.overflow = "visible";
+ }
+ }
+
+ // add content to restricted container
+ container.appendChild(content);
+
+ // append container to body for rendering
+ if (superContainer) {
+ containerElement.appendChild(superContainer);
+ } else {
+ containerElement.appendChild(container);
+ }
+
+ // calculate scroll width of content and add corners and shadow width
+ if (!w) {
+ w = parseInt(content.scrollWidth);
+
+ // update container width to allow height to adjust
+ container.style.width = w + "px";
+ }
+ // capture height and add shadow and corner image widths
+ if (!h) {
+ h = parseInt(content.scrollHeight);
+ }
+
+ // remove elements
+ container.removeChild(content);
+ if (superContainer) {
+ superContainer.removeChild(container);
+ containerElement.removeChild(superContainer);
+ } else {
+ containerElement.removeChild(container);
+ }
+
+ return new OpenLayers.Size(w, h);
+};
+
+/**
+ * APIFunction: getScrollbarWidth
+ * This function has been modified by the OpenLayers from the original version,
+ * written by Matthew Eernisse and released under the Apache 2
+ * license here:
+ *
+ * http://www.fleegix.org/articles/2006/05/30/getting-the-scrollbar-width-in-pixels
+ *
+ * It has been modified simply to cache its value, since it is physically
+ * impossible that this code could ever run in more than one browser at
+ * once.
+ *
+ * Returns:
+ * {Integer}
+ */
+OpenLayers.Util.getScrollbarWidth = function() {
+
+ var scrollbarWidth = OpenLayers.Util._scrollbarWidth;
+
+ if (scrollbarWidth == null) {
+ var scr = null;
+ var inn = null;
+ var wNoScroll = 0;
+ var wScroll = 0;
+
+ // Outer scrolling div
+ scr = document.createElement('div');
+ scr.style.position = 'absolute';
+ scr.style.top = '-1000px';
+ scr.style.left = '-1000px';
+ scr.style.width = '100px';
+ scr.style.height = '50px';
+ // Start with no scrollbar
+ scr.style.overflow = 'hidden';
+
+ // Inner content div
+ inn = document.createElement('div');
+ inn.style.width = '100%';
+ inn.style.height = '200px';
+
+ // Put the inner div in the scrolling div
+ scr.appendChild(inn);
+ // Append the scrolling div to the doc
+ document.body.appendChild(scr);
+
+ // Width of the inner div sans scrollbar
+ wNoScroll = inn.offsetWidth;
+
+ // Add the scrollbar
+ scr.style.overflow = 'scroll';
+ // Width of the inner div width scrollbar
+ wScroll = inn.offsetWidth;
+
+ // Remove the scrolling div from the doc
+ document.body.removeChild(document.body.lastChild);
+
+ // Pixel width of the scroller
+ OpenLayers.Util._scrollbarWidth = (wNoScroll - wScroll);
+ scrollbarWidth = OpenLayers.Util._scrollbarWidth;
+ }
+
+ return scrollbarWidth;
+};
+
+/**
+ * APIFunction: getFormattedLonLat
+ * This function will return latitude or longitude value formatted as
+ *
+ * Parameters:
+ * coordinate - {Float} the coordinate value to be formatted
+ * axis - {String} value of either 'lat' or 'lon' to indicate which axis is to
+ * to be formatted (default = lat)
+ * dmsOption - {String} specify the precision of the output can be one of:
+ * 'dms' show degrees minutes and seconds
+ * 'dm' show only degrees and minutes
+ * 'd' show only degrees
+ *
+ * Returns:
+ * {String} the coordinate value formatted as a string
+ */
+OpenLayers.Util.getFormattedLonLat = function(coordinate, axis, dmsOption) {
+ if (!dmsOption) {
+ dmsOption = 'dms'; //default to show degree, minutes, seconds
+ }
+
+ coordinate = (coordinate+540)%360 - 180; // normalize for sphere being round
+
+ var abscoordinate = Math.abs(coordinate);
+ var coordinatedegrees = Math.floor(abscoordinate);
+
+ var coordinateminutes = (abscoordinate - coordinatedegrees)/(1/60);
+ var tempcoordinateminutes = coordinateminutes;
+ coordinateminutes = Math.floor(coordinateminutes);
+ var coordinateseconds = (tempcoordinateminutes - coordinateminutes)/(1/60);
+ coordinateseconds = Math.round(coordinateseconds*10);
+ coordinateseconds /= 10;
+
+ if( coordinateseconds >= 60) {
+ coordinateseconds -= 60;
+ coordinateminutes += 1;
+ if( coordinateminutes >= 60) {
+ coordinateminutes -= 60;
+ coordinatedegrees += 1;
+ }
+ }
+
+ if( coordinatedegrees < 10 ) {
+ coordinatedegrees = "0" + coordinatedegrees;
+ }
+ var str = coordinatedegrees + "\u00B0";
+
+ if (dmsOption.indexOf('dm') >= 0) {
+ if( coordinateminutes < 10 ) {
+ coordinateminutes = "0" + coordinateminutes;
+ }
+ str += coordinateminutes + "'";
+
+ if (dmsOption.indexOf('dms') >= 0) {
+ if( coordinateseconds < 10 ) {
+ coordinateseconds = "0" + coordinateseconds;
+ }
+ str += coordinateseconds + '"';
+ }
+ }
+
+ if (axis == "lon") {
+ str += coordinate < 0 ? OpenLayers.i18n("W") : OpenLayers.i18n("E");
+ } else {
+ str += coordinate < 0 ? OpenLayers.i18n("S") : OpenLayers.i18n("N");
+ }
+ return str;
+};
+
+/* ======================================================================
+ OpenLayers/Events.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Namespace: OpenLayers.Event
+ * Utility functions for event handling.
+ */
+OpenLayers.Event = {
+
+ /**
+ * Property: observers
+ * {Object} A hashtable cache of the event observers. Keyed by
+ * element._eventCacheID
+ */
+ observers: false,
+
+ /**
+ * Constant: KEY_SPACE
+ * {int}
+ */
+ KEY_SPACE: 32,
+
+ /**
+ * Constant: KEY_BACKSPACE
+ * {int}
+ */
+ KEY_BACKSPACE: 8,
+
+ /**
+ * Constant: KEY_TAB
+ * {int}
+ */
+ KEY_TAB: 9,
+
+ /**
+ * Constant: KEY_RETURN
+ * {int}
+ */
+ KEY_RETURN: 13,
+
+ /**
+ * Constant: KEY_ESC
+ * {int}
+ */
+ KEY_ESC: 27,
+
+ /**
+ * Constant: KEY_LEFT
+ * {int}
+ */
+ KEY_LEFT: 37,
+
+ /**
+ * Constant: KEY_UP
+ * {int}
+ */
+ KEY_UP: 38,
+
+ /**
+ * Constant: KEY_RIGHT
+ * {int}
+ */
+ KEY_RIGHT: 39,
+
+ /**
+ * Constant: KEY_DOWN
+ * {int}
+ */
+ KEY_DOWN: 40,
+
+ /**
+ * Constant: KEY_DELETE
+ * {int}
+ */
+ KEY_DELETE: 46,
+
+
+ /**
+ * Method: element
+ * Cross browser event element detection.
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {DOMElement} The element that caused the event
+ */
+ element: function(event) {
+ return event.target || event.srcElement;
+ },
+
+ /**
+ * Method: isSingleTouch
+ * Determine whether event was caused by a single touch
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ isSingleTouch: function(event) {
+ return event.touches && event.touches.length == 1;
+ },
+
+ /**
+ * Method: isMultiTouch
+ * Determine whether event was caused by a multi touch
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ isMultiTouch: function(event) {
+ return event.touches && event.touches.length > 1;
+ },
+
+ /**
+ * Method: isLeftClick
+ * Determine whether event was caused by a left click.
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ isLeftClick: function(event) {
+ return (((event.which) && (event.which == 1)) ||
+ ((event.button) && (event.button == 1)));
+ },
+
+ /**
+ * Method: isRightClick
+ * Determine whether event was caused by a right mouse click.
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ isRightClick: function(event) {
+ return (((event.which) && (event.which == 3)) ||
+ ((event.button) && (event.button == 2)));
+ },
+
+ /**
+ * Method: stop
+ * Stops an event from propagating.
+ *
+ * Parameters:
+ * event - {Event}
+ * allowDefault - {Boolean} If true, we stop the event chain but
+ * still allow the default browser behaviour (text selection,
+ * radio-button clicking, etc). Default is false.
+ */
+ stop: function(event, allowDefault) {
+
+ if (!allowDefault) {
+ OpenLayers.Event.preventDefault(event);
+ }
+
+ if (event.stopPropagation) {
+ event.stopPropagation();
+ } else {
+ event.cancelBubble = true;
+ }
+ },
+
+ /**
+ * Method: preventDefault
+ * Cancels the event if it is cancelable, without stopping further
+ * propagation of the event.
+ *
+ * Parameters:
+ * event - {Event}
+ */
+ preventDefault: function(event) {
+ if (event.preventDefault) {
+ event.preventDefault();
+ } else {
+ event.returnValue = false;
+ }
+ },
+
+ /**
+ * Method: findElement
+ *
+ * Parameters:
+ * event - {Event}
+ * tagName - {String}
+ *
+ * Returns:
+ * {DOMElement} The first node with the given tagName, starting from the
+ * node the event was triggered on and traversing the DOM upwards
+ */
+ findElement: function(event, tagName) {
+ var element = OpenLayers.Event.element(event);
+ while (element.parentNode && (!element.tagName ||
+ (element.tagName.toUpperCase() != tagName.toUpperCase()))){
+ element = element.parentNode;
+ }
+ return element;
+ },
+
+ /**
+ * Method: observe
+ *
+ * Parameters:
+ * elementParam - {DOMElement || String}
+ * name - {String}
+ * observer - {function}
+ * useCapture - {Boolean}
+ */
+ observe: function(elementParam, name, observer, useCapture) {
+ var element = OpenLayers.Util.getElement(elementParam);
+ useCapture = useCapture || false;
+
+ if (name == 'keypress' &&
+ (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
+ || element.attachEvent)) {
+ name = 'keydown';
+ }
+
+ //if observers cache has not yet been created, create it
+ if (!this.observers) {
+ this.observers = {};
+ }
+
+ //if not already assigned, make a new unique cache ID
+ if (!element._eventCacheID) {
+ var idPrefix = "eventCacheID_";
+ if (element.id) {
+ idPrefix = element.id + "_" + idPrefix;
+ }
+ element._eventCacheID = OpenLayers.Util.createUniqueID(idPrefix);
+ }
+
+ var cacheID = element._eventCacheID;
+
+ //if there is not yet a hash entry for this element, add one
+ if (!this.observers[cacheID]) {
+ this.observers[cacheID] = [];
+ }
+
+ //add a new observer to this element's list
+ this.observers[cacheID].push({
+ 'element': element,
+ 'name': name,
+ 'observer': observer,
+ 'useCapture': useCapture
+ });
+
+ //add the actual browser event listener
+ if (element.addEventListener) {
+ element.addEventListener(name, observer, useCapture);
+ } else if (element.attachEvent) {
+ element.attachEvent('on' + name, observer);
+ }
+ },
+
+ /**
+ * Method: stopObservingElement
+ * Given the id of an element to stop observing, cycle through the
+ * element's cached observers, calling stopObserving on each one,
+ * skipping those entries which can no longer be removed.
+ *
+ * parameters:
+ * elementParam - {DOMElement || String}
+ */
+ stopObservingElement: function(elementParam) {
+ var element = OpenLayers.Util.getElement(elementParam);
+ var cacheID = element._eventCacheID;
+
+ this._removeElementObservers(OpenLayers.Event.observers[cacheID]);
+ },
+
+ /**
+ * Method: _removeElementObservers
+ *
+ * Parameters:
+ * elementObservers - {Array(Object)} Array of (element, name,
+ * observer, usecapture) objects,
+ * taken directly from hashtable
+ */
+ _removeElementObservers: function(elementObservers) {
+ if (elementObservers) {
+ for(var i = elementObservers.length-1; i >= 0; i--) {
+ var entry = elementObservers[i];
+ OpenLayers.Event.stopObserving.apply(this, [
+ entry.element, entry.name, entry.observer, entry.useCapture
+ ]);
+ }
+ }
+ },
+
+ /**
+ * Method: stopObserving
+ *
+ * Parameters:
+ * elementParam - {DOMElement || String}
+ * name - {String}
+ * observer - {function}
+ * useCapture - {Boolean}
+ *
+ * Returns:
+ * {Boolean} Whether or not the event observer was removed
+ */
+ stopObserving: function(elementParam, name, observer, useCapture) {
+ useCapture = useCapture || false;
+
+ var element = OpenLayers.Util.getElement(elementParam);
+ var cacheID = element._eventCacheID;
+
+ if (name == 'keypress') {
+ if ( navigator.appVersion.match(/Konqueror|Safari|KHTML/) ||
+ element.detachEvent) {
+ name = 'keydown';
+ }
+ }
+
+ // find element's entry in this.observers cache and remove it
+ var foundEntry = false;
+ var elementObservers = OpenLayers.Event.observers[cacheID];
+ if (elementObservers) {
+
+ // find the specific event type in the element's list
+ var i=0;
+ while(!foundEntry && i < elementObservers.length) {
+ var cacheEntry = elementObservers[i];
+
+ if ((cacheEntry.name == name) &&
+ (cacheEntry.observer == observer) &&
+ (cacheEntry.useCapture == useCapture)) {
+
+ elementObservers.splice(i, 1);
+ if (elementObservers.length == 0) {
+ delete OpenLayers.Event.observers[cacheID];
+ }
+ foundEntry = true;
+ break;
+ }
+ i++;
+ }
+ }
+
+ //actually remove the event listener from browser
+ if (foundEntry) {
+ if (element.removeEventListener) {
+ element.removeEventListener(name, observer, useCapture);
+ } else if (element && element.detachEvent) {
+ element.detachEvent('on' + name, observer);
+ }
+ }
+ return foundEntry;
+ },
+
+ /**
+ * Method: unloadCache
+ * Cycle through all the element entries in the events cache and call
+ * stopObservingElement on each.
+ */
+ unloadCache: function() {
+ // check for OpenLayers.Event before checking for observers, because
+ // OpenLayers.Event may be undefined in IE if no map instance was
+ // created
+ if (OpenLayers.Event && OpenLayers.Event.observers) {
+ for (var cacheID in OpenLayers.Event.observers) {
+ var elementObservers = OpenLayers.Event.observers[cacheID];
+ OpenLayers.Event._removeElementObservers.apply(this,
+ [elementObservers]);
+ }
+ OpenLayers.Event.observers = false;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Event"
+};
+
+/* prevent memory leaks in IE */
+OpenLayers.Event.observe(window, 'unload', OpenLayers.Event.unloadCache, false);
+
+/**
+ * Class: OpenLayers.Events
+ */
+OpenLayers.Events = OpenLayers.Class({
+
+ /**
+ * Constant: BROWSER_EVENTS
+ * {Array(String)} supported events
+ */
+ BROWSER_EVENTS: [
+ "mouseover", "mouseout",
+ "mousedown", "mouseup", "mousemove",
+ "click", "dblclick", "rightclick", "dblrightclick",
+ "resize", "focus", "blur",
+ "touchstart", "touchmove", "touchend",
+ "keydown"
+ ],
+
+ /**
+ * Property: listeners
+ * {Object} Hashtable of Array(Function): events listener functions
+ */
+ listeners: null,
+
+ /**
+ * Property: object
+ * {Object} the code object issuing application events
+ */
+ object: null,
+
+ /**
+ * Property: element
+ * {DOMElement} the DOM element receiving browser events
+ */
+ element: null,
+
+ /**
+ * Property: eventHandler
+ * {Function} bound event handler attached to elements
+ */
+ eventHandler: null,
+
+ /**
+ * APIProperty: fallThrough
+ * {Boolean}
+ */
+ fallThrough: null,
+
+ /**
+ * APIProperty: includeXY
+ * {Boolean} Should the .xy property automatically be created for browser
+ * mouse events? In general, this should be false. If it is true, then
+ * mouse events will automatically generate a '.xy' property on the
+ * event object that is passed. (Prior to OpenLayers 2.7, this was true
+ * by default.) Otherwise, you can call the getMousePosition on the
+ * relevant events handler on the object available via the 'evt.object'
+ * property of the evt object. So, for most events, you can call:
+ * function named(evt) {
+ * this.xy = this.object.events.getMousePosition(evt)
+ * }
+ *
+ * This option typically defaults to false for performance reasons:
+ * when creating an events object whose primary purpose is to manage
+ * relatively positioned mouse events within a div, it may make
+ * sense to set it to true.
+ *
+ * This option is also used to control whether the events object caches
+ * offsets. If this is false, it will not: the reason for this is that
+ * it is only expected to be called many times if the includeXY property
+ * is set to true. If you set this to true, you are expected to clear
+ * the offset cache manually (using this.clearMouseCache()) if:
+ * the border of the element changes
+ * the location of the element in the page changes
+ */
+ includeXY: false,
+
+ /**
+ * APIProperty: extensions
+ * {Object} Event extensions registered with this instance. Keys are
+ * event types, values are {OpenLayers.Events.*} extension instances or
+ * {Boolean} for events that an instantiated extension provides in
+ * addition to the one it was created for.
+ *
+ * Extensions create an event in addition to browser events, which usually
+ * fires when a sequence of browser events is completed. Extensions are
+ * automatically instantiated when a listener is registered for an event
+ * provided by an extension.
+ *
+ * Extensions are created in the <OpenLayers.Events> namespace using
+ * <OpenLayers.Class>, and named after the event they provide.
+ * The constructor receives the target <OpenLayers.Events> instance as
+ * argument. Extensions that need to capture browser events before they
+ * propagate can register their listeners events using <register>, 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 <listeners>), values are the
+ * number of extension listeners for each event type.
+ */
+ extensionCount: null,
+
+ /**
+ * Method: clearMouseListener
+ * A version of <clearMouseCache> that is bound to this instance so that
+ * it can be used with <OpenLayers.Event.observe> and
+ * <OpenLayers.Event.stopObserving>.
+ */
+ 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 <register> 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 <unregister> 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<len; i++) {
+ if (listeners[i].obj == obj && listeners[i].func == func) {
+ listeners.splice(i, 1);
+ break;
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: remove
+ * Remove all listeners for a given event type. If type is not registered,
+ * does nothing.
+ *
+ * Parameters:
+ * type - {String}
+ */
+ remove: function(type) {
+ if (this.listeners[type] != null) {
+ this.listeners[type] = [];
+ }
+ },
+
+ /**
+ * APIMethod: triggerEvent
+ * Trigger a specified registered event.
+ *
+ * Parameters:
+ * type - {String}
+ * evt - {Event || Object} will be passed to the listeners.
+ *
+ * Returns:
+ * {Boolean} The last listener return. If a listener returns false, the
+ * chain of listeners will stop getting called.
+ */
+ triggerEvent: function (type, evt) {
+ var listeners = this.listeners[type];
+
+ // fast path
+ if(!listeners || listeners.length == 0) {
+ return undefined;
+ }
+
+ // prep evt object with object & div references
+ if (evt == null) {
+ evt = {};
+ }
+ evt.object = this.object;
+ evt.element = this.element;
+ if(!evt.type) {
+ evt.type = type;
+ }
+
+ // execute all callbacks registered for specified type
+ // get a clone of the listeners array to
+ // allow for splicing during callbacks
+ listeners = listeners.slice();
+ var continueChain;
+ for (var i=0, len=listeners.length; i<len; i++) {
+ var callback = listeners[i];
+ // bind the context to callback.obj
+ continueChain = callback.func.apply(callback.obj, [evt]);
+
+ if ((continueChain != undefined) && (continueChain == false)) {
+ // if callback returns false, execute no more callbacks.
+ break;
+ }
+ }
+ // don't fall through to other DOM elements
+ if (!this.fallThrough) {
+ OpenLayers.Event.stop(evt, true);
+ }
+ return continueChain;
+ },
+
+ /**
+ * Method: handleBrowserEvent
+ * Basically just a wrapper to the triggerEvent() function, but takes
+ * care to set a property 'xy' on the event with the current mouse
+ * position.
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ handleBrowserEvent: function (evt) {
+ var type = evt.type, listeners = this.listeners[type];
+ if(!listeners || listeners.length == 0) {
+ // noone's listening, bail out
+ return;
+ }
+ // add clientX & clientY to all events - corresponds to average x, y
+ var touches = evt.touches;
+ if (touches && touches[0]) {
+ var x = 0;
+ var y = 0;
+ var num = touches.length;
+ var touch;
+ for (var i=0; i<num; ++i) {
+ touch = this.getTouchClientXY(touches[i]);
+ x += touch.clientX;
+ y += touch.clientY;
+ }
+ evt.clientX = x / num;
+ evt.clientY = y / num;
+ }
+ if (this.includeXY) {
+ evt.xy = this.getMousePosition(evt);
+ }
+ this.triggerEvent(type, evt);
+ },
+
+ /**
+ * Method: getTouchClientXY
+ * WebKit has a few bugs for clientX/clientY. This method detects them
+ * and calculate the correct values.
+ *
+ * Parameters:
+ * evt - {Touch} a Touch object from a TouchEvent
+ *
+ * Returns:
+ * {Object} An object with only clientX and clientY properties with the
+ * calculated values.
+ */
+ getTouchClientXY: function (evt) {
+ // olMochWin is to override window, used for testing
+ var win = window.olMockWin || window,
+ winPageX = win.pageXOffset,
+ winPageY = win.pageYOffset,
+ x = evt.clientX,
+ y = evt.clientY;
+
+ if (evt.pageY === 0 && Math.floor(y) > 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:
+ * {<OpenLayers.Pixel>} 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<ii; ++i) {
+ if (touches[i].pointerId == e.pointerId) {
+ alreadyInArray = true;
+ break;
+ }
+ }
+ if (!alreadyInArray) {
+ touches.push(e);
+ }
+
+ e.touches = touches.slice();
+ handler(e);
+ };
+
+ OpenLayers.Event.observe(element, 'MSPointerDown', cb);
+
+ // Need to also listen for end events to keep the _msTouches list
+ // accurate
+ var internalCb = function(e) {
+ for (var i=0, ii=touches.length; i<ii; ++i) {
+ if (touches[i].pointerId == e.pointerId) {
+ touches.splice(i, 1);
+ break;
+ }
+ }
+ };
+ OpenLayers.Event.observe(element, 'MSPointerUp', internalCb);
+ },
+
+ /**
+ * Method: addMsTouchListenerMove
+ *
+ * Parameters:
+ * element - {DOMElement} The DOM element to register the listener on
+ * type - {String} The event type
+ * handler - {Function} the handler
+ */
+ addMsTouchListenerMove: function (element, type, handler) {
+ var touches = this._msTouches;
+ var cb = function(e) {
+
+ //Don't fire touch moves when mouse isn't down
+ if (e.pointerType == e.MSPOINTER_TYPE_MOUSE && e.buttons == 0) {
+ return;
+ }
+
+ if (touches.length == 1 && touches[0].pageX == e.pageX &&
+ touches[0].pageY == e.pageY) {
+ // don't trigger event when pointer has not moved
+ return;
+ }
+ for (var i=0, ii=touches.length; i<ii; ++i) {
+ if (touches[i].pointerId == e.pointerId) {
+ touches[i] = e;
+ break;
+ }
+ }
+
+ e.touches = touches.slice();
+ handler(e);
+ };
+
+ OpenLayers.Event.observe(element, 'MSPointerMove', cb);
+ },
+
+ /**
+ * Method: addMsTouchListenerEnd
+ *
+ * Parameters:
+ * element - {DOMElement} The DOM element to register the listener on
+ * type - {String} The event type
+ * handler - {Function} the handler
+ */
+ addMsTouchListenerEnd: function (element, type, handler) {
+ var touches = this._msTouches;
+
+ var cb = function(e) {
+
+ for (var i=0, ii=touches.length; i<ii; ++i) {
+ if (touches[i].pointerId == e.pointerId) {
+ touches.splice(i, 1);
+ break;
+ }
+ }
+
+ e.touches = touches.slice();
+ handler(e);
+ };
+
+ OpenLayers.Event.observe(element, 'MSPointerUp', cb);
+ },
+
+ CLASS_NAME: "OpenLayers.Events"
+});
+/* ======================================================================
+ OpenLayers/Events/buttonclick.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.buttonclick
+ * Extension event type for handling buttons on top of a dom element. This
+ * event type fires "buttonclick" on its <target> 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 <element>.
+ *
+ * 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
+ * {<OpenLayers.Events>} 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
+ * <OpenLayers.Events> instances.
+ *
+ * Parameters:
+ * target - {<OpenLayers.Events>} The events instance that the buttonclick
+ * event will be triggered on.
+ */
+ initialize: function(target) {
+ this.target = target;
+ for (var i=this.events.length-1; i>=0; --i) {
+ this.target.register(this.events[i], this, this.buttonClick, {
+ extension: true
+ });
+ }
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ for (var i=this.events.length-1; i>=0; --i) {
+ this.target.unregister(this.events[i], this, this.buttonClick);
+ }
+ delete this.target;
+ },
+
+ /**
+ * Method: getPressedButton
+ * Get the pressed button, if any. Returns undefined if no button
+ * was pressed.
+ *
+ * Arguments:
+ * element - {DOMElement} The event target.
+ *
+ * Returns:
+ * {DOMElement} The button element, or undefined.
+ */
+ getPressedButton: function(element) {
+ var depth = 3, // limit the search depth
+ button;
+ do {
+ if(OpenLayers.Element.hasClass(element, "olButton")) {
+ // hit!
+ button = element;
+ break;
+ }
+ element = element.parentNode;
+ } while(--depth > 0 && element);
+ return button;
+ },
+
+ /**
+ * Method: ignore
+ * Check for event target elements that should be ignored by OpenLayers.
+ *
+ * Parameters:
+ * element - {DOMElement} The event target.
+ */
+ ignore: function(element) {
+ var depth = 3,
+ ignore = false;
+ do {
+ if (element.nodeName.toLowerCase() === 'a') {
+ ignore = true;
+ break;
+ }
+ element = element.parentNode;
+ } while (--depth > 0 && element);
+ return ignore;
+ },
+
+ /**
+ * Method: buttonClick
+ * Check if a button was clicked, and fire the buttonclick event
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ buttonClick: function(evt) {
+ var propagate = true,
+ element = OpenLayers.Event.element(evt);
+ if (element && (OpenLayers.Event.isLeftClick(evt) || !~evt.type.indexOf("mouse"))) {
+ // was a button pressed?
+ var button = this.getPressedButton(element);
+ if (button) {
+ if (evt.type === "keydown") {
+ switch (evt.keyCode) {
+ case OpenLayers.Event.KEY_RETURN:
+ case OpenLayers.Event.KEY_SPACE:
+ this.target.triggerEvent("buttonclick", {
+ buttonElement: button
+ });
+ OpenLayers.Event.stop(evt);
+ propagate = false;
+ break;
+ }
+ } else if (this.startEvt) {
+ if (this.completeRegEx.test(evt.type)) {
+ var pos = OpenLayers.Util.pagePosition(button);
+ var viewportElement = OpenLayers.Util.getViewportElement();
+ var scrollTop = window.pageYOffset || viewportElement.scrollTop;
+ var scrollLeft = window.pageXOffset || viewportElement.scrollLeft;
+ pos[0] = pos[0] - scrollLeft;
+ pos[1] = pos[1] - scrollTop;
+
+ this.target.triggerEvent("buttonclick", {
+ buttonElement: button,
+ buttonXY: {
+ x: this.startEvt.clientX - pos[0],
+ y: this.startEvt.clientY - pos[1]
+ }
+ });
+ }
+ if (this.cancelRegEx.test(evt.type)) {
+ delete this.startEvt;
+ }
+ OpenLayers.Event.stop(evt);
+ propagate = false;
+ }
+ if (this.startRegEx.test(evt.type)) {
+ this.startEvt = evt;
+ OpenLayers.Event.stop(evt);
+ propagate = false;
+ }
+ } else {
+ propagate = !this.ignore(OpenLayers.Event.element(evt));
+ delete this.startEvt;
+ }
+ }
+ return propagate;
+ }
+
+});
+/* ======================================================================
+ OpenLayers/Control.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Control
+ * Controls affect the display or behavior of the map. They allow everything
+ * from panning and zooming to displaying a scale indicator. Controls by
+ * default are added to the map they are contained within however it is
+ * possible to add a control to an external div by passing the div in the
+ * options parameter.
+ *
+ * Example:
+ * The following example shows how to add many of the common controls
+ * to a map.
+ *
+ * > var map = new OpenLayers.Map('map', { controls: [] });
+ * >
+ * > map.addControl(new OpenLayers.Control.PanZoomBar());
+ * > map.addControl(new OpenLayers.Control.LayerSwitcher({'ascending':false}));
+ * > map.addControl(new OpenLayers.Control.Permalink());
+ * > map.addControl(new OpenLayers.Control.Permalink('permalink'));
+ * > map.addControl(new OpenLayers.Control.MousePosition());
+ * > map.addControl(new OpenLayers.Control.OverviewMap());
+ * > map.addControl(new OpenLayers.Control.KeyboardDefaults());
+ *
+ * The next code fragment is a quick example of how to intercept
+ * shift-mouse click to display the extent of the bounding box
+ * dragged out by the user. Usually controls are not created
+ * in exactly this manner. See the source for a more complete
+ * example:
+ *
+ * > var control = new OpenLayers.Control();
+ * > OpenLayers.Util.extend(control, {
+ * > draw: function () {
+ * > // this Handler.Box will intercept the shift-mousedown
+ * > // before Control.MouseDefault gets to see it
+ * > this.box = new OpenLayers.Handler.Box( control,
+ * > {"done": this.notice},
+ * > {keyMask: OpenLayers.Handler.MOD_SHIFT});
+ * > this.box.activate();
+ * > },
+ * >
+ * > notice: function (bounds) {
+ * > OpenLayers.Console.userError(bounds);
+ * > }
+ * > });
+ * > map.addControl(control);
+ *
+ */
+OpenLayers.Control = OpenLayers.Class({
+
+ /**
+ * Property: id
+ * {String}
+ */
+ id: null,
+
+ /**
+ * Property: map
+ * {<OpenLayers.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
+ * <OpenLayers.Control.Panel>.
+ */
+ 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 <activate> and
+ * <deactivate> to change control state.
+ */
+ active: null,
+
+ /**
+ * Property: handlerOptions
+ * {Object} Used to set non-default properties on the control's handler
+ */
+ handlerOptions: null,
+
+ /**
+ * Property: handler
+ * {<OpenLayers.Handler>} null
+ */
+ handler: null,
+
+ /**
+ * APIProperty: eventListeners
+ * {Object} If set as an option at construction, the eventListeners
+ * object will be registered with <OpenLayers.Events.on>. Object
+ * structure must be a listeners object as shown in the example for
+ * the events.on method.
+ */
+ eventListeners: null,
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.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 - {<OpenLayers.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 - {<OpenLayers.Pixel>} 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 - {<OpenLayers.Pixel>}
+ */
+ moveTo: function (px) {
+ if ((px != null) && (this.div != null)) {
+ this.div.style.left = px.x + "px";
+ this.div.style.top = px.y + "px";
+ }
+ },
+
+ /**
+ * APIMethod: activate
+ * Explicitly activates a control and it's associated
+ * handler if one has been set. Controls can be
+ * deactivated by calling the deactivate() method.
+ *
+ * Returns:
+ * {Boolean} True if the control was successfully activated or
+ * false if the control was already active.
+ */
+ activate: function () {
+ if (this.active) {
+ return false;
+ }
+ if (this.handler) {
+ this.handler.activate();
+ }
+ this.active = true;
+ if(this.map) {
+ OpenLayers.Element.addClass(
+ this.map.viewPortDiv,
+ this.displayClass.replace(/ /g, "") + "Active"
+ );
+ }
+ this.events.triggerEvent("activate");
+ return true;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivates a control and it's associated handler if any. The exact
+ * effect of this depends on the control itself.
+ *
+ * Returns:
+ * {Boolean} True if the control was effectively deactivated or false
+ * if the control was already inactive.
+ */
+ deactivate: function () {
+ if (this.active) {
+ if (this.handler) {
+ this.handler.deactivate();
+ }
+ this.active = false;
+ if(this.map) {
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv,
+ this.displayClass.replace(/ /g, "") + "Active"
+ );
+ }
+ this.events.triggerEvent("deactivate");
+ return true;
+ }
+ return false;
+ },
+
+ CLASS_NAME: "OpenLayers.Control"
+});
+
+/**
+ * Constant: OpenLayers.Control.TYPE_BUTTON
+ */
+OpenLayers.Control.TYPE_BUTTON = 1;
+
+/**
+ * Constant: OpenLayers.Control.TYPE_TOGGLE
+ */
+OpenLayers.Control.TYPE_TOGGLE = 2;
+
+/**
+ * Constant: OpenLayers.Control.TYPE_TOOL
+ */
+OpenLayers.Control.TYPE_TOOL = 3;
+/* ======================================================================
+ OpenLayers/Geometry.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry
+ * A Geometry is a description of a geographic object. Create an instance of
+ * this class with the <OpenLayers.Geometry> constructor. This is a base class,
+ * typical geometry types are described by subclasses of this class.
+ *
+ * Note that if you use the <OpenLayers.Geometry.fromWKT> 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
+ * {<OpenLayers.Geometry>}This is set when a Geometry is added as component
+ * of another geometry
+ */
+ parent: null,
+
+ /**
+ * Property: bounds
+ * {<OpenLayers.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:
+ * {<OpenLayers.Geometry>} An exact clone of this geometry.
+ */
+ clone: function() {
+ return new OpenLayers.Geometry();
+ },
+
+ /**
+ * Method: setBounds
+ * Set the bounds for this Geometry.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.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 - {<OpenLayers.Bounds>}
+ */
+ 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:
+ * {<OpenLayers.Bounds>}
+ */
+ 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 - {<OpenLayers.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 - {<OpenLayers.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:
+ * {<OpenLayers.Geometry.Point>} 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:
+ * {<OpenLayers.Geometry>} 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<len; ++i) {
+ components[i] = result[i].geometry;
+ }
+ geom = new OpenLayers.Geometry.Collection(components);
+ }
+ }
+ return geom;
+};
+
+/**
+ * Method: OpenLayers.Geometry.segmentsIntersect
+ * Determine whether two line segments intersect. Optionally calculates
+ * and returns the intersection point. This function is optimized for
+ * cases where seg1.x2 >= 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 | <OpenLayers.Geometry.Point>} The two segments intersect.
+ * If the point argument is true, the return will be the intersection
+ * point or false if none exists. If point is true and the segments
+ * are coincident, return will be true (and the instersection is equal
+ * to the shorter segment).
+ */
+OpenLayers.Geometry.segmentsIntersect = function(seg1, seg2, options) {
+ var point = options && options.point;
+ var tolerance = options && options.tolerance;
+ var intersection = false;
+ var x11_21 = seg1.x1 - seg2.x1;
+ var y11_21 = seg1.y1 - seg2.y1;
+ var x12_11 = seg1.x2 - seg1.x1;
+ var y12_11 = seg1.y2 - seg1.y1;
+ var y22_21 = seg2.y2 - seg2.y1;
+ var x22_21 = seg2.x2 - seg2.x1;
+ var d = (y22_21 * x12_11) - (x22_21 * y12_11);
+ var n1 = (x22_21 * y11_21) - (y22_21 * x11_21);
+ var n2 = (x12_11 * y11_21) - (y12_11 * x11_21);
+ if(d == 0) {
+ // parallel
+ if(n1 == 0 && n2 == 0) {
+ // coincident
+ intersection = true;
+ }
+ } else {
+ var along1 = n1 / d;
+ var along2 = n2 / d;
+ if(along1 >= 0 && along1 <= 1 && along2 >=0 && along2 <= 1) {
+ // intersect
+ if(!point) {
+ intersection = true;
+ } else {
+ // calculate the intersection point
+ var x = seg1.x1 + (along1 * x12_11);
+ var y = seg1.y1 + (along1 * y12_11);
+ intersection = new OpenLayers.Geometry.Point(x, y);
+ }
+ }
+ }
+ if(tolerance) {
+ var dist;
+ if(intersection) {
+ if(point) {
+ var segs = [seg1, seg2];
+ var seg, x, y;
+ // check segment endpoints for proximity to intersection
+ // set intersection to first endpoint within the tolerance
+ outer: for(var i=0; i<2; ++i) {
+ seg = segs[i];
+ for(var j=1; j<3; ++j) {
+ x = seg["x" + j];
+ y = seg["y" + j];
+ dist = Math.sqrt(
+ Math.pow(x - intersection.x, 2) +
+ Math.pow(y - intersection.y, 2)
+ );
+ if(dist < tolerance) {
+ intersection.x = x;
+ intersection.y = y;
+ break outer;
+ }
+ }
+ }
+
+ }
+ } else {
+ // no calculated intersection, but segments could be within
+ // the tolerance of one another
+ var segs = [seg1, seg2];
+ var source, target, x, y, p, result;
+ // check segment endpoints for proximity to intersection
+ // set intersection to first endpoint within the tolerance
+ outer: for(var i=0; i<2; ++i) {
+ source = segs[i];
+ target = segs[(i+1)%2];
+ for(var j=1; j<3; ++j) {
+ p = {x: source["x"+j], y: source["y"+j]};
+ result = OpenLayers.Geometry.distanceToSegment(p, target);
+ if(result.distance < tolerance) {
+ if(point) {
+ intersection = new OpenLayers.Geometry.Point(p.x, p.y);
+ } else {
+ intersection = true;
+ }
+ break outer;
+ }
+ }
+ }
+ }
+ }
+ return intersection;
+};
+
+/**
+ * Function: OpenLayers.Geometry.distanceToSegment
+ *
+ * Parameters:
+ * point - {Object} An object with x and y properties representing the
+ * point coordinates.
+ * segment - {Object} An object with x1, y1, x2, and y2 properties
+ * representing endpoint coordinates.
+ *
+ * Returns:
+ * {Object} An object with distance, along, x, and y properties. The distance
+ * will be the shortest distance between the input point and segment.
+ * The x and y properties represent the coordinates along the segment
+ * where the shortest distance meets the segment. The along attribute
+ * describes how far between the two segment points the given point is.
+ */
+OpenLayers.Geometry.distanceToSegment = function(point, segment) {
+ var result = OpenLayers.Geometry.distanceSquaredToSegment(point, segment);
+ result.distance = Math.sqrt(result.distance);
+ return result;
+};
+
+/**
+ * Function: OpenLayers.Geometry.distanceSquaredToSegment
+ *
+ * Usually the distanceToSegment function should be used. This variant however
+ * can be used for comparisons where the exact distance is not important.
+ *
+ * Parameters:
+ * point - {Object} An object with x and y properties representing the
+ * point coordinates.
+ * segment - {Object} An object with x1, y1, x2, and y2 properties
+ * representing endpoint coordinates.
+ *
+ * Returns:
+ * {Object} An object with squared distance, along, x, and y properties.
+ * The distance will be the shortest distance between the input point and
+ * segment. The x and y properties represent the coordinates along the
+ * segment where the shortest distance meets the segment. The along
+ * attribute describes how far between the two segment points the given
+ * point is.
+ */
+OpenLayers.Geometry.distanceSquaredToSegment = function(point, segment) {
+ var x0 = point.x;
+ var y0 = point.y;
+ var x1 = segment.x1;
+ var y1 = segment.y1;
+ var x2 = segment.x2;
+ var y2 = segment.y2;
+ var dx = x2 - x1;
+ var dy = y2 - y1;
+ var along = ((dx * (x0 - x1)) + (dy * (y0 - y1))) /
+ (Math.pow(dx, 2) + Math.pow(dy, 2));
+ var x, y;
+ if(along <= 0.0) {
+ x = x1;
+ y = y1;
+ } else if(along >= 1.0) {
+ x = x2;
+ y = y2;
+ } else {
+ x = x1 + along * dx;
+ y = y1 + along * dy;
+ }
+ return {
+ distance: Math.pow(x - x0, 2) + Math.pow(y - y0, 2),
+ x: x, y: y,
+ along: along
+ };
+};
+/* ======================================================================
+ OpenLayers/Geometry/Collection.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Collection
+ * A Collection is exactly what it sounds like: A collection of different
+ * Geometries. These are stored in the local parameter <components> (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 <getArea> and <getLength> functions here merely iterate through
+ * the components, summing their respective areas and lengths.
+ *
+ * Create a new instance with the <OpenLayers.Geometry.Collection> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry>
+ */
+OpenLayers.Geometry.Collection = OpenLayers.Class(OpenLayers.Geometry, {
+
+ /**
+ * APIProperty: components
+ * {Array(<OpenLayers.Geometry>)} 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(<OpenLayers.Geometry>)} 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:
+ * {<OpenLayers.Geometry.Collection>} An exact clone of this collection
+ */
+ clone: function() {
+ var geometry = eval("new " + this.CLASS_NAME + "()");
+ for(var i=0, len=this.components.length; i<len; i++) {
+ geometry.addComponent(this.components[i].clone());
+ }
+
+ // catch any randomly tagged-on properties
+ OpenLayers.Util.applyDefaults(geometry, this);
+
+ return geometry;
+ },
+
+ /**
+ * Method: getComponentsString
+ * Get a string representing the components for this collection
+ *
+ * Returns:
+ * {String} A string representation of the components of this geometry
+ */
+ getComponentsString: function(){
+ var strings = [];
+ for(var i=0, len=this.components.length; i<len; i++) {
+ strings.push(this.components[i].toShortString());
+ }
+ return strings.join(",");
+ },
+
+ /**
+ * APIMethod: calculateBounds
+ * Recalculate the bounds by iterating through the components and
+ * calling calling extendBounds() on each item.
+ */
+ calculateBounds: function() {
+ this.bounds = null;
+ var bounds = new OpenLayers.Bounds();
+ var components = this.components;
+ if (components) {
+ for (var i=0, len=components.length; i<len; i++) {
+ bounds.extend(components[i].getBounds());
+ }
+ }
+ // to preserve old behavior, we only set bounds if non-null
+ // in the future, we could add bounds.isEmpty()
+ if (bounds.left != null && bounds.bottom != null &&
+ bounds.right != null && bounds.top != null) {
+ this.setBounds(bounds);
+ }
+ },
+
+ /**
+ * APIMethod: addComponents
+ * Add components to this geometry.
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry>)} An array of geometries to add
+ */
+ addComponents: function(components){
+ if(!(OpenLayers.Util.isArray(components))) {
+ components = [components];
+ }
+ for(var i=0, len=components.length; i<len; i++) {
+ this.addComponent(components[i]);
+ }
+ },
+
+ /**
+ * Method: addComponent
+ * Add a new component (geometry) to the collection. If this.componentTypes
+ * is set, then the component class name must be in the componentTypes array.
+ *
+ * The bounds cache is reset.
+ *
+ * Parameters:
+ * component - {<OpenLayers.Geometry>} 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(<OpenLayers.Geometry>)} 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 - {<OpenLayers.Geometry>}
+ *
+ * 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<len; i++) {
+ length += this.components[i].getLength();
+ }
+ return length;
+ },
+
+ /**
+ * APIMethod: getArea
+ * Calculate the area of this geometry. Note how this function is overridden
+ * in <OpenLayers.Geometry.Polygon>.
+ *
+ * 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<len; i++) {
+ area += this.components[i].getArea();
+ }
+ return area;
+ },
+
+ /**
+ * APIMethod: getGeodesicArea
+ * Calculate the approximate area of the polygon were it projected onto
+ * the earth.
+ *
+ * Parameters:
+ * projection - {<OpenLayers.Projection>} 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<len; i++) {
+ area += this.components[i].getGeodesicArea(projection);
+ }
+ return area;
+ },
+
+ /**
+ * APIMethod: getCentroid
+ *
+ * Compute the centroid for this geometry collection.
+ *
+ * Parameters:
+ * weighted - {Boolean} Perform the getCentroid computation recursively,
+ * returning an area weighted average of all geometries in this collection.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} 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<len; ++i) {
+ component = this.components[i];
+ var area = component.getArea();
+ var centroid = component.getCentroid(true);
+ if (isNaN(area) || isNaN(centroid.x) || isNaN(centroid.y)) {
+ continue;
+ }
+ areas.push(area);
+ areaSum += area;
+ minArea = (area < minArea && area > 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<len; ++i) {
+ areas[i] = 1;
+ }
+ areaSum = areas.length;
+ } else {
+ // normalize all the areas where the smallest area will get
+ // a value of 1
+ for (var i=0; i<len; ++i) {
+ areas[i] /= minArea;
+ }
+ areaSum /= minArea;
+ }
+
+ var xSum = 0, ySum = 0, centroid, area;
+ for (var i=0; i<len; ++i) {
+ centroid = centroids[i];
+ area = areas[i];
+ xSum += centroid.x * area;
+ ySum += centroid.y * area;
+ }
+
+ return new OpenLayers.Geometry.Point(xSum/areaSum, ySum/areaSum);
+ },
+
+ /**
+ * APIMethod: getGeodesicLength
+ * Calculate the approximate length of the geometry were it projected onto
+ * the earth.
+ *
+ * projection - {<OpenLayers.Projection>} 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<len; i++) {
+ length += this.components[i].getGeodesicLength(projection);
+ }
+ return length;
+ },
+
+ /**
+ * 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<len; i++) {
+ this.components[i].move(x, y);
+ }
+ },
+
+ /**
+ * APIMethod: rotate
+ * Rotate a geometry around some origin
+ *
+ * Parameters:
+ * angle - {Float} Rotation angle in degrees (measured counterclockwise
+ * from the positive x-axis)
+ * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
+ */
+ rotate: function(angle, origin) {
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ this.components[i].rotate(angle, origin);
+ }
+ },
+
+ /**
+ * APIMethod: resize
+ * Resize a geometry relative to some origin. Use this method to apply
+ * a uniform scaling to a geometry.
+ *
+ * Parameters:
+ * scale - {Float} Factor by which to scale the geometry. A scale of 2
+ * doubles the size of the geometry in each dimension
+ * (lines, for example, will be twice as long, and polygons
+ * will have four times the area).
+ * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
+ * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} - The current geometry.
+ */
+ resize: function(scale, origin, ratio) {
+ for(var i=0; i<this.components.length; ++i) {
+ this.components[i].resize(scale, origin, ratio);
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Calculate the closest distance between two geometries (on the x-y plane).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.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 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<len; ++i) {
+ result = this.components[i].distanceTo(geometry, options);
+ distance = details ? result.distance : result;
+ if(distance < min) {
+ min = distance;
+ best = result;
+ if(min == 0) {
+ break;
+ }
+ }
+ }
+ return best;
+ },
+
+ /**
+ * APIMethod: equals
+ * Determine whether another geometry is equivalent to this one. Geometries
+ * are considered equivalent if all components have the same coordinates.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} 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<len; ++i) {
+ if(!this.components[i].equals(geometry.components[i])) {
+ equivalent = false;
+ break;
+ }
+ }
+ }
+ return equivalent;
+ },
+
+ /**
+ * APIMethod: transform
+ * Reproject the components geometry from source to dest.
+ *
+ * Parameters:
+ * source - {<OpenLayers.Projection>}
+ * dest - {<OpenLayers.Projection>}
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>}
+ */
+ transform: function(source, dest) {
+ if (source && dest) {
+ for (var i=0, len=this.components.length; i<len; i++) {
+ var component = this.components[i];
+ component.transform(source, dest);
+ }
+ this.bounds = null;
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: intersects
+ * Determine if the input geometry intersects this one.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} 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<len; ++ i) {
+ intersect = geometry.intersects(this.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ return intersect;
+ },
+
+ /**
+ * 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 = [];
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ Array.prototype.push.apply(
+ vertices, this.components[i].getVertices(nodes)
+ );
+ }
+ return vertices;
+ },
+
+
+ CLASS_NAME: "OpenLayers.Geometry.Collection"
+});
+/* ======================================================================
+ 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>
+ */
+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:
+ * {<OpenLayers.Geometry.Point>} 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 - {<OpenLayers.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 - {<OpenLayers.Geometry.Point>} 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. <i>"5, 42"</i>)
+ */
+ 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 - {<OpenLayers.Geometry.Point>} 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:
+ * {<OpenLayers.Geometry.Point>} 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 - {<OpenLayers.Geometry.Point>} Point of origin for resizing
+ * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} - 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 - {<OpenLayers.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 - {<OpenLayers.Projection>}
+ * dest - {<OpenLayers.Projection>}
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>}
+ */
+ transform: function(source, dest) {
+ if ((source && dest)) {
+ OpenLayers.Projection.transform(
+ this, source, dest);
+ this.bounds = null;
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: getVertices
+ * Return a list of all points in this geometry.
+ *
+ * Parameters:
+ * nodes - {Boolean} For lines, only return vertices that are
+ * endpoints. If false, for lines, only vertices that are not
+ * endpoints will be returned. If not provided, all vertices will
+ * be returned.
+ *
+ * Returns:
+ * {Array} A list of all vertices in the geometry.
+ */
+ getVertices: function(nodes) {
+ return [this];
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.Point"
+});
+/* ======================================================================
+ OpenLayers/Geometry/MultiPoint.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ * @requires OpenLayers/Geometry/Point.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.MultiPoint
+ * MultiPoint is a collection of Points. Create a new instance with the
+ * <OpenLayers.Geometry.MultiPoint> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Collection>
+ * - <OpenLayers.Geometry>
+ */
+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(<OpenLayers.Geometry.Point>)}
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.MultiPoint>}
+ */
+
+ /**
+ * APIMethod: addPoint
+ * Wrapper for <OpenLayers.Geometry.Collection.addComponent>
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>} Point to be added
+ * index - {Integer} Optional index
+ */
+ addPoint: function(point, index) {
+ this.addComponent(point, index);
+ },
+
+ /**
+ * APIMethod: removePoint
+ * Wrapper for <OpenLayers.Geometry.Collection.removeComponent>
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.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.MultiPoint>
+ */
+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(<OpenLayers.Geometry.Point>)}
+ */
+
+ /**
+ * 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<len; i++) {
+ length += this.components[i-1].distanceTo(this.components[i]);
+ }
+ }
+ return length;
+ },
+
+ /**
+ * APIMethod: getGeodesicLength
+ * Calculate the approximate length of the geometry were it projected onto
+ * the earth.
+ *
+ * projection - {<OpenLayers.Projection>} 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<len; i++) {
+ p1 = geom.components[i-1];
+ p2 = geom.components[i];
+ // this returns km and requires lon/lat properties
+ length += OpenLayers.Util.distVincenty(
+ {lon: p1.x, lat: p1.y}, {lon: p2.x, lat: p2.y}
+ );
+ }
+ }
+ // convert to m
+ return length * 1000;
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.Curve"
+});
+/* ======================================================================
+ OpenLayers/Geometry/LineString.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/Curve.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.LineString
+ * A LineString is a Curve which, once two points have been added to it, can
+ * never be less than two points long.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Curve>
+ */
+OpenLayers.Geometry.LineString = OpenLayers.Class(OpenLayers.Geometry.Curve, {
+
+ /**
+ * Constructor: OpenLayers.Geometry.LineString
+ * Create a new LineString geometry
+ *
+ * Parameters:
+ * points - {Array(<OpenLayers.Geometry.Point>)} 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 - {<OpenLayers.Geometry.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 - {<OpenLayers.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<len; ++i) {
+ seg1 = segs1[i];
+ seg1x1 = seg1.x1;
+ seg1x2 = seg1.x2;
+ seg1y1 = seg1.y1;
+ seg1y2 = seg1.y2;
+ inner: for(var j=0, jlen=segs2.length; j<jlen; ++j) {
+ seg2 = segs2[j];
+ if(seg2.x1 > 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<numSeg; ++i) {
+ point1 = this.components[i];
+ point2 = this.components[i + 1];
+ if(point1.x < point2.x) {
+ segments[i] = {
+ x1: point1.x,
+ y1: point1.y,
+ x2: point2.x,
+ y2: point2.y
+ };
+ } else {
+ segments[i] = {
+ x1: point2.x,
+ y1: point2.y,
+ x2: point1.x,
+ y2: point1.y
+ };
+ }
+ }
+ // more efficient to define this somewhere static
+ function byX1(seg1, seg2) {
+ return seg1.x1 - seg2.x1;
+ }
+ return segments.sort(byX1);
+ },
+
+ /**
+ * Method: splitWithSegment
+ * Split this geometry with the given segment.
+ *
+ * Parameters:
+ * seg - {Object} An object with x1, y1, x2, and y2 properties referencing
+ * segment endpoint coordinates.
+ * options - {Object} Properties of this object will be used to determine
+ * how the split is conducted.
+ *
+ * Valid options:
+ * edge - {Boolean} Allow splitting when only edges intersect. Default is
+ * true. If false, a vertex on the source segment 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 one of the source segment's
+ * endpoints will be assumed to occur at the endpoint.
+ *
+ * Returns:
+ * {Object} An object with *lines* and *points* properties. If the given
+ * segment intersects this linestring, the lines array will reference
+ * geometries that result from the split. The points array will contain
+ * all intersection points. Intersection points are sorted along the
+ * segment (in order from x1,y1 to x2,y2).
+ */
+ splitWithSegment: function(seg, options) {
+ var edge = !(options && options.edge === false);
+ var tolerance = options && options.tolerance;
+ var lines = [];
+ var verts = this.getVertices();
+ var points = [];
+ var intersections = [];
+ var split = false;
+ var vert1, vert2, point;
+ var node, vertex, target;
+ var interOptions = {point: true, tolerance: tolerance};
+ var result = null;
+ for(var i=0, stop=verts.length-2; i<=stop; ++i) {
+ vert1 = verts[i];
+ points.push(vert1.clone());
+ vert2 = verts[i+1];
+ target = {x1: vert1.x, y1: vert1.y, x2: vert2.x, y2: vert2.y};
+ point = OpenLayers.Geometry.segmentsIntersect(
+ seg, target, interOptions
+ );
+ if(point instanceof OpenLayers.Geometry.Point) {
+ if((point.x === seg.x1 && point.y === seg.y1) ||
+ (point.x === seg.x2 && point.y === seg.y2) ||
+ point.equals(vert1) || point.equals(vert2)) {
+ vertex = true;
+ } else {
+ vertex = false;
+ }
+ if(vertex || edge) {
+ // push intersections different than the previous
+ if(!point.equals(intersections[intersections.length-1])) {
+ intersections.push(point.clone());
+ }
+ if(i === 0) {
+ if(point.equals(vert1)) {
+ continue;
+ }
+ }
+ if(point.equals(vert2)) {
+ continue;
+ }
+ split = true;
+ if(!point.equals(vert1)) {
+ points.push(point);
+ }
+ lines.push(new OpenLayers.Geometry.LineString(points));
+ points = [point.clone()];
+ }
+ }
+ }
+ if(split) {
+ points.push(vert2.clone());
+ lines.push(new OpenLayers.Geometry.LineString(points));
+ }
+ if(intersections.length > 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 - {<OpenLayers.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(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<targetParts.length; ++j) {
+ splits = targetParts[j].splitWithSegment(seg, options);
+ if(splits) {
+ // splice in new features
+ lines = splits.lines;
+ if(lines.length > 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<len; ++k) {
+ point = splits.points[k];
+ if(!point.equals(vert1)) {
+ points.push(point);
+ sourceParts.push(new OpenLayers.Geometry.LineString(points));
+ if(point.equals(vert2)) {
+ points = [];
+ } else {
+ points = [point.clone()];
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if(mutual && sourceParts.length > 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 - {<OpenLayers.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 - {<OpenLayers.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<len; ++i) {
+ seg = segs[i];
+ result = OpenLayers.Geometry.distanceToSegment(geometry, seg);
+ if(result.distance < min) {
+ min = result.distance;
+ best = result;
+ if(min === 0) {
+ break;
+ }
+ } else {
+ // if distance increases and we cross y0 to the right of x0, no need to keep looking.
+ if(seg.x2 > 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<len; ++i) {
+ seg0 = segs0[i];
+ x0 = seg0.x1;
+ y0 = seg0.y1;
+ for(var j=0; j<len1; ++j) {
+ seg1 = segs1[j];
+ intersection = OpenLayers.Geometry.segmentsIntersect(seg0, seg1, interOptions);
+ if(intersection) {
+ min = 0;
+ best = {
+ distance: 0,
+ x0: intersection.x, y0: intersection.y,
+ x1: intersection.x, y1: intersection.y
+ };
+ break outer;
+ } else {
+ result = OpenLayers.Geometry.distanceToSegment({x: x0, y: y0}, seg1);
+ if(result.distance < min) {
+ min = result.distance;
+ best = {
+ distance: min,
+ x0: x0, y0: y0,
+ x1: result.x, y1: result.y
+ };
+ }
+ }
+ }
+ }
+ if(!details) {
+ best = best.distance;
+ }
+ if(min !== 0) {
+ // check the final vertex in this line's sorted segments
+ if(seg0) {
+ result = geometry.distanceTo(
+ new OpenLayers.Geometry.Point(seg0.x2, seg0.y2),
+ options
+ );
+ var dist = details ? result.distance : result;
+ if(dist < min) {
+ if(details) {
+ best = {
+ distance: min,
+ x0: result.x1, y0: result.y1,
+ x1: result.x0, y1: result.y0
+ };
+ } else {
+ best = dist;
+ }
+ }
+ }
+ }
+ } else {
+ best = geometry.distanceTo(this, options);
+ // swap since target comes from this line
+ if(details) {
+ best = {
+ distance: best.distance,
+ x0: best.x1, y0: best.y1,
+ x1: best.x0, y1: best.y0
+ };
+ }
+ }
+ return best;
+ },
+
+ /**
+ * APIMethod: simplify
+ * This function will return a simplified LineString.
+ * Simplification is based on the Douglas-Peucker algorithm.
+ *
+ *
+ * Parameters:
+ * tolerance - {number} threshhold for simplification in map units
+ *
+ * Returns:
+ * {OpenLayers.Geometry.LineString} the simplified LineString
+ */
+ simplify: function(tolerance){
+ if (this && this !== null) {
+ var points = this.getVertices();
+ if (points.length < 3) {
+ return this;
+ }
+
+ var compareNumbers = function(a, b){
+ return (a-b);
+ };
+
+ /**
+ * Private function doing the Douglas-Peucker reduction
+ */
+ var douglasPeuckerReduction = function(points, firstPoint, lastPoint, tolerance){
+ var maxDistance = 0;
+ var indexFarthest = 0;
+
+ for (var index = firstPoint, distance; index < lastPoint; index++) {
+ distance = perpendicularDistance(points[firstPoint], points[lastPoint], points[index]);
+ if (distance > maxDistance) {
+ maxDistance = distance;
+ indexFarthest = index;
+ }
+ }
+
+ if (maxDistance > tolerance && indexFarthest != firstPoint) {
+ //Add the largest point that exceeds the tolerance
+ pointIndexsToKeep.push(indexFarthest);
+ douglasPeuckerReduction(points, firstPoint, indexFarthest, tolerance);
+ douglasPeuckerReduction(points, indexFarthest, lastPoint, tolerance);
+ }
+ };
+
+ /**
+ * Private function calculating the perpendicular distance
+ * TODO: check whether OpenLayers.Geometry.LineString::distanceTo() is faster or slower
+ */
+ var perpendicularDistance = function(point1, point2, point){
+ //Area = |(1/2)(x1y2 + x2y3 + x3y1 - x2y1 - x3y2 - x1y3)| *Area of triangle
+ //Base = v((x1-x2)²+(x1-x2)²) *Base of Triangle*
+ //Area = .5*Base*H *Solve for height
+ //Height = Area/.5/Base
+
+ var area = Math.abs(0.5 * (point1.x * point2.y + point2.x * point.y + point.x * point1.y - point2.x * point1.y - point.x * point2.y - point1.x * point.y));
+ var bottom = Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2));
+ var height = area / bottom * 2;
+
+ return height;
+ };
+
+ var firstPoint = 0;
+ var lastPoint = points.length - 1;
+ var pointIndexsToKeep = [];
+
+ //Add the first and last index to the keepers
+ pointIndexsToKeep.push(firstPoint);
+ pointIndexsToKeep.push(lastPoint);
+
+ //The first and the last point cannot be the same
+ while (points[firstPoint].equals(points[lastPoint])) {
+ lastPoint--;
+ //Addition: the first point not equal to first point in the LineString is kept as well
+ pointIndexsToKeep.push(lastPoint);
+ }
+
+ douglasPeuckerReduction(points, firstPoint, lastPoint, tolerance);
+ var returnPoints = [];
+ pointIndexsToKeep.sort(compareNumbers);
+ for (var index = 0; index < pointIndexsToKeep.length; index++) {
+ returnPoints.push(points[pointIndexsToKeep[index]]);
+ }
+ return new OpenLayers.Geometry.LineString(returnPoints);
+
+ }
+ else {
+ return this;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.LineString"
+});
+/* ======================================================================
+ OpenLayers/Geometry/LinearRing.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/LineString.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.LinearRing
+ *
+ * A Linear Ring is a special LineString which is closed. It closes itself
+ * automatically on every addPoint/removePoint by adding a copy of the first
+ * point as the last point.
+ *
+ * Also, as it is the first in the line family to close itself, a getArea()
+ * function is defined to calculate the enclosed area of the linearRing
+ *
+ * Inherits:
+ * - <OpenLayers.Geometry.LineString>
+ */
+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(<OpenLayers.Geometry.Point>)} 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 - {<OpenLayers.Geometry.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 - {<OpenLayers.Geometry.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<len - 1; i++) {
+ this.components[i].move(x, y);
+ }
+ },
+
+ /**
+ * APIMethod: rotate
+ * Rotate a geometry around some origin
+ *
+ * Parameters:
+ * angle - {Float} Rotation angle in degrees (measured counterclockwise
+ * from the positive x-axis)
+ * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
+ */
+ rotate: function(angle, origin) {
+ for(var i=0, len=this.components.length; i<len - 1; ++i) {
+ this.components[i].rotate(angle, origin);
+ }
+ },
+
+ /**
+ * APIMethod: resize
+ * Resize a geometry relative to some origin. Use this method to apply
+ * a uniform scaling to a geometry.
+ *
+ * Parameters:
+ * scale - {Float} Factor by which to scale the geometry. A scale of 2
+ * doubles the size of the geometry in each dimension
+ * (lines, for example, will be twice as long, and polygons
+ * will have four times the area).
+ * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
+ * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} - The current geometry.
+ */
+ resize: function(scale, origin, ratio) {
+ for(var i=0, len=this.components.length; i<len - 1; ++i) {
+ this.components[i].resize(scale, origin, ratio);
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: transform
+ * Reproject the components geometry from source to dest.
+ *
+ * Parameters:
+ * source - {<OpenLayers.Projection>}
+ * dest - {<OpenLayers.Projection>}
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>}
+ */
+ transform: function(source, dest) {
+ if (source && dest) {
+ for (var i=0, len=this.components.length; i<len - 1; i++) {
+ var component = this.components[i];
+ component.transform(source, dest);
+ }
+ this.bounds = null;
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: getCentroid
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} 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<len - 1; i++) {
+ var b = this.components[i];
+ var c = this.components[i+1];
+ sum += (b.x + c.x) * (c.y - b.y);
+ }
+ area = - sum / 2.0;
+ }
+ return area;
+ },
+
+ /**
+ * APIMethod: getGeodesicArea
+ * Calculate the approximate area of the polygon were it projected onto
+ * the earth. Note that this area will be positive if ring is oriented
+ * clockwise, otherwise it will be negative.
+ *
+ * Parameters:
+ * projection - {<OpenLayers.Projection>} 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<len-1; i++) {
+ p1 = ring.components[i];
+ p2 = ring.components[i+1];
+ area += OpenLayers.Util.rad(p2.x - p1.x) *
+ (2 + Math.sin(OpenLayers.Util.rad(p1.y)) +
+ Math.sin(OpenLayers.Util.rad(p2.y)));
+ }
+ area = area * 6378137.0 * 6378137.0 / 2.0;
+ }
+ return area;
+ },
+
+ /**
+ * Method: containsPoint
+ * Test if a point is inside a linear ring. For the case where a point
+ * is coincident with a linear ring edge, returns 1. Otherwise,
+ * returns boolean.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * 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<numSeg; ++i) {
+ start = this.components[i];
+ x1 = approx(start.x, digs);
+ y1 = approx(start.y, digs);
+ end = this.components[i + 1];
+ x2 = approx(end.x, digs);
+ y2 = approx(end.y, digs);
+
+ /**
+ * The following conditions enforce five edge-crossing rules:
+ * 1. points coincident with edges are considered contained;
+ * 2. an upward edge includes its starting endpoint, and
+ * excludes its final endpoint;
+ * 3. a downward edge excludes its starting endpoint, and
+ * includes its final endpoint;
+ * 4. horizontal edges are excluded; and
+ * 5. the edge-ray intersection point must be strictly right
+ * of the point P.
+ */
+ if(y1 == y2) {
+ // horizontal edge
+ if(py == y1) {
+ // point on horizontal line
+ if(x1 <= x2 && (px >= 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 - {<OpenLayers.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<len; ++ i) {
+ intersect = geometry.components[i].intersects(this);
+ if(intersect) {
+ break;
+ }
+ }
+ }
+ return intersect;
+ },
+
+ /**
+ * 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 (nodes === true) ? [] : this.components.slice(0, this.components.length-1);
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.LinearRing"
+});
+/* ======================================================================
+ 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<l; i++) {
+ prefix = VENDOR_PREFIXES[i];
+ if(prefix) {
+ if (!isStyleObj) {
+ // js prefix should be lower-case, while style
+ // properties have upper case on first character
+ prefix = prefix.toLowerCase();
+ }
+ tmpProp = prefix + property.charAt(0).toUpperCase() + property.slice(1);
+ } else {
+ tmpProp = property;
+ }
+
+ if(obj[tmpProp] !== undefined) {
+ jsCache[property] = tmpProp;
+ break;
+ }
+ }
+ }
+ return jsCache[property];
+ }
+
+ /**
+ * APIMethod: style
+ * Detect which property is used for a DOM style property
+ *
+ * Parameters:
+ * property - {String} The standard (unprefixed) style property name
+ *
+ * Returns:
+ * {String} The standard style property, prefixed property or null if not
+ * supported
+ */
+ function style(property) {
+ return js(divStyle, property);
+ }
+
+ return {
+ css: css,
+ js: js,
+ style: style,
+
+ // used for testing
+ cssCache: cssCache,
+ jsCache: jsCache
+ };
+}());
+/* ======================================================================
+ OpenLayers/Animation.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
+ * @requires OpenLayers/Util/vendorPrefix.js
+ */
+
+/**
+ * Namespace: OpenLayers.Animation
+ * A collection of utility functions for executing methods that repaint a
+ * portion of the browser window. These methods take advantage of the
+ * browser's scheduled repaints where requestAnimationFrame is available.
+ */
+OpenLayers.Animation = (function(window) {
+
+ /**
+ * Property: isNative
+ * {Boolean} true if a native requestAnimationFrame function is available
+ */
+ var requestAnimationFrame = OpenLayers.Util.vendorPrefix.js(window, "requestAnimationFrame");
+ var isNative = !!(requestAnimationFrame);
+
+ /**
+ * Function: requestFrame
+ * Schedule a function to be called at the next available animation frame.
+ * Uses the native method where available. Where requestAnimationFrame is
+ * not available, setTimeout will be called with a 16ms delay.
+ *
+ * Parameters:
+ * callback - {Function} The function to be called at the next animation frame.
+ * element - {DOMElement} Optional element that visually bounds the animation.
+ */
+ var requestFrame = (function() {
+ var request = window[requestAnimationFrame] ||
+ function(callback, element) {
+ window.setTimeout(callback, 16);
+ };
+ // bind to window to avoid illegal invocation of native function
+ return function(callback, element) {
+ request.apply(window, [callback, element]);
+ };
+ })();
+
+ // private variables for animation loops
+ var counter = 0;
+ var loops = {};
+
+ /**
+ * Function: start
+ * Executes a method with <requestFrame> 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
+ * <stop>.
+ */
+ 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 <start>.
+ *
+ * Parameters:
+ * id - {Number} Identifier returned from <start>.
+ */
+ 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
+ * {<OpenLayers.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 - {<OpenLayers.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, <http://www.robertpenner.com/easing/>
+ */
+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 <transform> method for details
+ * on usage.
+ *
+ * Additional transforms may be added by using the <proj4js at http://proj4js.org/>
+ * library. If the proj4js library is included, the <transform> 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 <addTransform>
+ * 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:
+ * {<OpenLayers.Projection>} 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 <addTransform> 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 - {<OpenLayers.Geometry.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<len; ++i) {
+ code = codes[i];
+ add(base, code, forwardMercator);
+ add(code, base, inverseMercator);
+ for (j=i+1; j<len; ++j) {
+ other = codes[j];
+ add(code, other, same);
+ add(other, code, same);
+ }
+ }
+ }
+
+ // list of equivalent codes for web mercator
+ var mercator = ["EPSG:900913", "EPSG:3857", "EPSG:102113", "EPSG:102100"],
+ geographic = ["CRS:84", "urn:ogc:def:crs:EPSG:6.6:4326", "EPSG:4326"],
+ i;
+ for (i=mercator.length-1; 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 <OpenLayers.Map> constructor.
+ *
+ * On their own maps do not provide much functionality. To extend a map
+ * it's necessary to add controls (<OpenLayers.Control>) and
+ * layers (<OpenLayers.Layer>) to the map.
+ */
+OpenLayers.Map = OpenLayers.Class({
+
+ /**
+ * Constant: Z_INDEX_BASE
+ * {Object} Base z-indexes for different classes of thing
+ */
+ Z_INDEX_BASE: {
+ BaseLayer: 100,
+ Overlay: 325,
+ Feature: 725,
+ Popup: 750,
+ Control: 1000
+ },
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.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 - {<OpenLayers.Pixel>} 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 <updateSize> 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
+ * <getResolutionForZoom> instead of layer.resolutions[zoom] as the
+ * former works for non-integer zoom levels.
+ */
+ fractionalZoom: false,
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} An events object that handles all
+ * events on the map
+ */
+ events: null,
+
+ /**
+ * APIProperty: 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 <setLayerIndex> or <raiseLayer> 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 <OpenLayers.Map> 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 <render> method.
+ *
+ * Note:
+ * If you are calling <render> after map construction, do not use
+ * <maxResolution> auto. Instead, divide your <maxExtent> by your
+ * maximum expected dimension.
+ */
+ div: null,
+
+ /**
+ * Property: dragging
+ * {Boolean} The map is currently being dragged.
+ */
+ dragging: false,
+
+ /**
+ * Property: size
+ * {<OpenLayers.Size>} Size of the main div (this.div)
+ */
+ size: null,
+
+ /**
+ * Property: viewPortDiv
+ * {HTMLDivElement} The element that represents the map viewport
+ */
+ viewPortDiv: null,
+
+ /**
+ * Property: layerContainerOrigin
+ * {<OpenLayers.LonLat>} The lonlat at which the later container was
+ * re-initialized (on-zoom)
+ */
+ layerContainerOrigin: null,
+
+ /**
+ * Property: layerContainerDiv
+ * {HTMLDivElement} The element that contains the layers.
+ */
+ layerContainerDiv: null,
+
+ /**
+ * APIProperty: layers
+ * {Array(<OpenLayers.Layer>)} Ordered list of layers in the map
+ */
+ layers: null,
+
+ /**
+ * APIProperty: controls
+ * {Array(<OpenLayers.Control>)} 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:
+ * - <OpenLayers.Control.Navigation> or <OpenLayers.Control.TouchNavigation>
+ * - <OpenLayers.Control.Zoom> or <OpenLayers.Control.PanZoom>
+ * - <OpenLayers.Control.ArgParser>
+ * - <OpenLayers.Control.Attribution>
+ */
+ controls: null,
+
+ /**
+ * Property: popups
+ * {Array(<OpenLayers.Popup>)} List of popups associated with the map
+ */
+ popups: null,
+
+ /**
+ * APIProperty: baseLayer
+ * {<OpenLayers.Layer>} The currently selected base layer. This determines
+ * min/max zoom level, projection, etc.
+ */
+ baseLayer: null,
+
+ /**
+ * Property: center
+ * {<OpenLayers.LonLat>} The current center of the map
+ */
+ center: null,
+
+ /**
+ * Property: resolution
+ * {Float} The resolution of the map.
+ */
+ resolution: null,
+
+ /**
+ * Property: zoom
+ * {Integer} The current zoom level of the map
+ */
+ zoom: 0,
+
+ /**
+ * Property: panRatio
+ * {Float} The ratio of the current extent within
+ * which panning will tween.
+ */
+ panRatio: 1.5,
+
+ /**
+ * APIProperty: options
+ * {Object} The options object passed to the class constructor. Read-only.
+ */
+ options: null,
+
+ // Options
+
+ /**
+ * APIProperty: tileSize
+ * {<OpenLayers.Size>} Set in the map options to override the default tile
+ * size for this map.
+ */
+ tileSize: null,
+
+ /**
+ * APIProperty: projection
+ * {String} Set in the map options to 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 <baseLayer> and <getProjectionObject>).
+ */
+ 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 <tileSize>.
+ */
+ maxResolution: null,
+
+ /**
+ * APIProperty: minResolution
+ * {Float}
+ */
+ minResolution: null,
+
+ /**
+ * APIProperty: maxScale
+ * {Float}
+ */
+ maxScale: null,
+
+ /**
+ * APIProperty: minScale
+ * {Float}
+ */
+ minScale: null,
+
+ /**
+ * APIProperty: maxExtent
+ * {<OpenLayers.Bounds>|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 <restrictedExtent> instead.
+ * The value for <maxExtent> will change calculations for tile URLs.
+ */
+ maxExtent: null,
+
+ /**
+ * APIProperty: minExtent
+ * {<OpenLayers.Bounds>|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
+ * {<OpenLayers.Bounds>|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
+ * {<OpenLayers.Projection>} 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
+ * {<OpenLayers.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 {<OpenLayers.TileManager>}.
+ */
+
+ /**
+ * 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 <OpenLayers.Events.on>. Object
+ * structure must be a listeners object as shown in the example for
+ * the events.on method.
+ */
+ eventListeners: null,
+
+ /**
+ * Property: panTween
+ * {<OpenLayers.Tween>} 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
+ * {<OpenLayers.Tween>} 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
+ * {<OpenLayers.Bounds>} 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 <div> option is
+ * provided or if you intend to call the <render> method later.
+ * options - {Object} Optional object with properties to tag onto the map.
+ *
+ * Valid options (in addition to the listed API properties):
+ * center - {<OpenLayers.LonLat>|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 <layers> 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
+ * <layers> 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 - {<OpenLayers.Bounds>|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 <center> and <zoom> 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<len; ++i) {
+ if(OpenLayers.Util.isEquivalentUrl(nodes.item(i).href,
+ this.theme)) {
+ addNode = false;
+ break;
+ }
+ }
+ // only add a new node if one with an equivalent url hasn't already
+ // been added
+ if(addNode) {
+ var cssNode = document.createElement('link');
+ cssNode.setAttribute('rel', 'stylesheet');
+ cssNode.setAttribute('type', 'text/css');
+ cssNode.setAttribute('href', this.theme);
+ document.getElementsByTagName('head')[0].appendChild(cssNode);
+ }
+ }
+
+ if (this.controls == null) { // default controls
+ this.controls = [];
+ if (OpenLayers.Control != null) { // running full or lite?
+ // Navigation or TouchNavigation depending on what is in build
+ if (OpenLayers.Control.Navigation) {
+ this.controls.push(new OpenLayers.Control.Navigation());
+ } else if (OpenLayers.Control.TouchNavigation) {
+ this.controls.push(new OpenLayers.Control.TouchNavigation());
+ }
+ if (OpenLayers.Control.Zoom) {
+ this.controls.push(new OpenLayers.Control.Zoom());
+ } else if (OpenLayers.Control.PanZoom) {
+ this.controls.push(new OpenLayers.Control.PanZoom());
+ }
+
+ if (OpenLayers.Control.ArgParser) {
+ this.controls.push(new OpenLayers.Control.ArgParser());
+ }
+ if (OpenLayers.Control.Attribution) {
+ this.controls.push(new OpenLayers.Control.Attribution());
+ }
+ }
+ }
+
+ for(var i=0, len=this.controls.length; i<len; i++) {
+ this.addControlToMap(this.controls[i]);
+ }
+
+ this.popups = [];
+
+ this.unloadDestroy = OpenLayers.Function.bind(this.destroy, this);
+
+
+ // always call map.destroy()
+ OpenLayers.Event.observe(window, 'unload', this.unloadDestroy);
+
+ // add any initial layers
+ if (options && options.layers) {
+ /**
+ * If you have set options.center, the map center property will be
+ * set at this point. However, since setCenter has not been called,
+ * addLayers gets confused. So we delete the map center in this
+ * case. Because the check below uses options.center, it will
+ * be properly set below.
+ */
+ delete this.center;
+ delete this.zoom;
+ this.addLayers(options.layers);
+ // set center (and optionally zoom)
+ if (options.center && !this.getCenter()) {
+ // zoom can be undefined here
+ this.setCenter(options.center, options.zoom);
+ }
+ }
+
+ if (this.panMethod) {
+ this.panTween = new OpenLayers.Tween(this.panMethod);
+ }
+ if (this.zoomMethod && this.applyTransform.transform) {
+ this.zoomTween = new OpenLayers.Tween(this.zoomMethod);
+ }
+ },
+
+ /**
+ * APIMethod: getViewport
+ * Get the DOMElement representing the view port.
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ getViewport: function() {
+ return this.viewPortDiv;
+ },
+
+ /**
+ * APIMethod: render
+ * Render the map to a specified container.
+ *
+ * Parameters:
+ * div - {String|DOMElement} The container that the map should be rendered
+ * to. If different than the current container, the map viewport
+ * will be moved from the current to the new container.
+ */
+ render: function(div) {
+ this.div = OpenLayers.Util.getElement(div);
+ OpenLayers.Element.addClass(this.div, 'olMap');
+ this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);
+ this.div.appendChild(this.viewPortDiv);
+ this.updateSize();
+ },
+
+ /**
+ * Method: unloadDestroy
+ * Function that is called to destroy the map on page unload. stored here
+ * so that if map is manually destroyed, we can unregister this.
+ */
+ unloadDestroy: null,
+
+ /**
+ * Method: updateSizeDestroy
+ * When the map is destroyed, we need to stop listening to updateSize
+ * events: this method stores the function we need to unregister in
+ * non-IE browsers.
+ */
+ updateSizeDestroy: null,
+
+ /**
+ * APIMethod: destroy
+ * Destroy this map.
+ * Note that if you are using an application which removes a container
+ * of the map from the DOM, you need to ensure that you destroy the
+ * map *before* this happens; otherwise, the page unload handler
+ * will fail because the DOM elements that map.destroy() wants
+ * to clean up will be gone. (See
+ * http://trac.osgeo.org/openlayers/ticket/2277 for more information).
+ * This will apply to GeoExt and also to other applications which
+ * modify the DOM of the container of the OpenLayers Map.
+ */
+ destroy:function() {
+ // if unloadDestroy is null, we've already been destroyed
+ if (!this.unloadDestroy) {
+ return false;
+ }
+
+ // make sure panning doesn't continue after destruction
+ if(this.panTween) {
+ this.panTween.stop();
+ this.panTween = null;
+ }
+ // make sure zooming doesn't continue after destruction
+ if(this.zoomTween) {
+ this.zoomTween.stop();
+ this.zoomTween = null;
+ }
+
+ // map has been destroyed. dont do it again!
+ OpenLayers.Event.stopObserving(window, 'unload', this.unloadDestroy);
+ this.unloadDestroy = null;
+
+ if (this.updateSizeDestroy) {
+ OpenLayers.Event.stopObserving(window, 'resize',
+ this.updateSizeDestroy);
+ }
+
+ this.paddingForPopups = null;
+
+ if (this.controls != null) {
+ for (var i = this.controls.length - 1; i>=0; --i) {
+ this.controls[i].destroy();
+ }
+ this.controls = null;
+ }
+ if (this.layers != null) {
+ for (var i = this.layers.length - 1; i>=0; --i) {
+ //pass 'false' to destroy so that map wont try to set a new
+ // baselayer after each baselayer is removed
+ this.layers[i].destroy(false);
+ }
+ this.layers = null;
+ }
+ if (this.viewPortDiv && this.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:
+ * {<OpenLayers.Size>}
+ */
+ getTileSize: function() {
+ return this.tileSize;
+ },
+
+
+ /**
+ * APIMethod: getBy
+ * Get a list of objects given a property and a match item.
+ *
+ * Parameters:
+ * array - {String} A property on the map whose value is an array.
+ * property - {String} A property on each item of the given array.
+ * match - {String | Object} A string to match. Can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * match.test(map[array][i][property]) evaluates to true, the item will
+ * be included in the array returned. If no items are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array} An array of items where the given property matches the given
+ * criteria.
+ */
+ getBy: function(array, property, match) {
+ var test = (typeof match.test == "function");
+ var found = OpenLayers.Array.filter(this[array], function(item) {
+ return item[property] == match || (test && match.test(item[property]));
+ });
+ return found;
+ },
+
+ /**
+ * APIMethod: getLayersBy
+ * Get a list of layers with properties matching the given criteria.
+ *
+ * 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(<OpenLayers.Layer>)} A list of layers matching the given criteria.
+ * An empty array is returned if no matches are found.
+ */
+ getLayersBy: function(property, match) {
+ return this.getBy("layers", property, match);
+ },
+
+ /**
+ * APIMethod: getLayersByName
+ * Get a list of layers with names matching the given name.
+ *
+ * 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(<OpenLayers.Layer>)} A list of layers matching the given name.
+ * An empty array is returned if no matches are found.
+ */
+ getLayersByName: function(match) {
+ return this.getLayersBy("name", match);
+ },
+
+ /**
+ * APIMethod: getLayersByClass
+ * Get a list of layers of a given class (CLASS_NAME).
+ *
+ * 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(<OpenLayers.Layer>)} A list of layers matching the given class.
+ * An empty array is returned if no matches are found.
+ */
+ getLayersByClass: function(match) {
+ return this.getLayersBy("CLASS_NAME", match);
+ },
+
+ /**
+ * APIMethod: getControlsBy
+ * Get a list of controls with properties matching the given criteria.
+ *
+ * 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(<OpenLayers.Control>)} A list of controls matching the given
+ * criteria. An empty array is returned if no matches are found.
+ */
+ getControlsBy: function(property, match) {
+ return this.getBy("controls", property, match);
+ },
+
+ /**
+ * APIMethod: getControlsByClass
+ * Get a list of controls of a given class (CLASS_NAME).
+ *
+ * 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(<OpenLayers.Control>)} A list of controls matching the given class.
+ * An empty array is returned if no matches are found.
+ */
+ getControlsByClass: function(match) {
+ return this.getControlsBy("CLASS_NAME", match);
+ },
+
+ /********************************************************/
+ /* */
+ /* Layer Functions */
+ /* */
+ /* The following functions deal with adding and */
+ /* removing Layers to and from the Map */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: getLayer
+ * Get a layer based on its id
+ *
+ * Parameters:
+ * id - {String} A layer id
+ *
+ * Returns:
+ * {<OpenLayers.Layer>} The Layer with the corresponding id from the map's
+ * layer collection, or null if not found.
+ */
+ getLayer: function(id) {
+ var foundLayer = null;
+ for (var i=0, len=this.layers.length; i<len; i++) {
+ var layer = this.layers[i];
+ if (layer.id == id) {
+ foundLayer = layer;
+ break;
+ }
+ }
+ return foundLayer;
+ },
+
+ /**
+ * Method: setLayerZIndex
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ * zIdx - {int}
+ */
+ setLayerZIndex: function (layer, zIdx) {
+ layer.setZIndex(
+ this.Z_INDEX_BASE[layer.isBaseLayer ? 'BaseLayer' : 'Overlay']
+ + zIdx * 5 );
+ },
+
+ /**
+ * Method: resetLayersZIndex
+ * Reset each layer's z-index based on layer's array index
+ */
+ resetLayersZIndex: function() {
+ for (var i=0, len=this.layers.length; i<len; i++) {
+ var layer = this.layers[i];
+ this.setLayerZIndex(layer, i);
+ }
+ },
+
+ /**
+ * APIMethod: addLayer
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ *
+ * 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(<OpenLayers.Layer>)}
+ */
+ addLayers: function (layers) {
+ for (var i=0, len=layers.length; i<len; i++) {
+ this.addLayer(layers[i]);
+ }
+ },
+
+ /**
+ * APIMethod: removeLayer
+ * Removes a layer from the map by removing its visual element (the
+ * layer.div property), then removing it from the map's internal list
+ * of layers, setting the layer's map property to null.
+ *
+ * a "removelayer" event is triggered.
+ *
+ * very worthy of mention is that simply removing a layer from a map
+ * will not cause the removal of any popups which may have been created
+ * by the layer. this is due to the fact that it was decided at some
+ * point that popups would not belong to layers. thus there is no way
+ * for us to know here to which layer the popup belongs.
+ *
+ * A simple solution to this is simply to call destroy() on the layer.
+ * the default OpenLayers.Layer class's destroy() function
+ * automatically takes care to remove itself from whatever map it has
+ * been attached to.
+ *
+ * The correct solution is for the layer itself to register an
+ * event-handler on "removelayer" and when it is called, if it
+ * recognizes itself as the layer being removed, then it cycles through
+ * its own personal list of popups, removing them from the map.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ * setNewBaseLayer - {Boolean} Default is true
+ */
+ removeLayer: function(layer, setNewBaseLayer) {
+ if (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<len; i++) {
+ var iLayer = this.layers[i];
+ if (iLayer.isBaseLayer || this.allOverlays) {
+ this.setBaseLayer(iLayer);
+ break;
+ }
+ }
+ }
+ }
+
+ this.resetLayersZIndex();
+
+ this.events.triggerEvent("removelayer", {layer: layer});
+ layer.events.triggerEvent("removed", {map: this, layer: layer});
+ },
+
+ /**
+ * APIMethod: getNumLayers
+ *
+ * Returns:
+ * {Int} The number of layers attached to the map.
+ */
+ getNumLayers: function () {
+ return this.layers.length;
+ },
+
+ /**
+ * APIMethod: getLayerIndex
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ *
+ * Returns:
+ * {Integer} The current (zero-based) index of the given layer in the map's
+ * layer stack. Returns -1 if the layer isn't on the map.
+ */
+ getLayerIndex: function (layer) {
+ return OpenLayers.Util.indexOf(this.layers, layer);
+ },
+
+ /**
+ * APIMethod: setLayerIndex
+ * Move the given layer to the specified (zero-based) index in the layer
+ * list, changing its z-index in the map display. Use
+ * map.getLayerIndex() to find out the current index of a layer. Note
+ * that this cannot (or at least should not) be effectively used to
+ * raise base layers above overlays.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ * idx - {int}
+ */
+ setLayerIndex: function (layer, idx) {
+ var base = this.getLayerIndex(layer);
+ if (idx < 0) {
+ idx = 0;
+ } else if (idx > this.layers.length) {
+ idx = this.layers.length;
+ }
+ if (base != idx) {
+ this.layers.splice(base, 1);
+ this.layers.splice(idx, 0, layer);
+ for (var i=0, len=this.layers.length; i<len; i++) {
+ this.setLayerZIndex(this.layers[i], i);
+ }
+ this.events.triggerEvent("changelayer", {
+ layer: layer, property: "order"
+ });
+ if(this.allOverlays) {
+ if(idx === 0) {
+ this.setBaseLayer(layer);
+ } else if(this.baseLayer !== this.layers[0]) {
+ this.setBaseLayer(this.layers[0]);
+ }
+ }
+ }
+ },
+
+ /**
+ * APIMethod: raiseLayer
+ * Change the index of the given layer by delta. If delta is positive,
+ * the layer is moved up the map's layer stack; if delta is negative,
+ * the layer is moved down. Again, note that this cannot (or at least
+ * should not) be effectively used to raise base layers above overlays.
+ *
+ * Paremeters:
+ * layer - {<OpenLayers.Layer>}
+ * delta - {int}
+ */
+ raiseLayer: function (layer, delta) {
+ var idx = this.getLayerIndex(layer) + delta;
+ this.setLayerIndex(layer, idx);
+ },
+
+ /**
+ * APIMethod: setBaseLayer
+ * Allows user to specify one of the currently-loaded layers as the Map's
+ * new base layer.
+ *
+ * Parameters:
+ * newBaseLayer - {<OpenLayers.Layer>}
+ */
+ setBaseLayer: function(newBaseLayer) {
+
+ 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 - {<OpenLayers.Control>}
+ * px - {<OpenLayers.Pixel>}
+ */
+ 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(<OpenLayers.Control>)}
+ * pixels - {Array(<OpenLayers.Pixel>)}
+ */
+ addControls: function (controls, pixels) {
+ var pxs = (arguments.length === 1) ? [] : pixels;
+ for (var i=0, len=controls.length; i<len; i++) {
+ var ctrl = controls[i];
+ var px = (pxs[i]) ? pxs[i] : null;
+ this.addControl( ctrl, px );
+ }
+ },
+
+ /**
+ * Method: addControlToMap
+ *
+ * Parameters:
+ *
+ * control - {<OpenLayers.Control>}
+ * px - {<OpenLayers.Pixel>}
+ */
+ addControlToMap: function (control, px) {
+ // If a control doesn't have a div at this point, it belongs in the
+ // viewport.
+ control.outsideViewport = (control.div != null);
+
+ // If the map has a displayProjection, and the control doesn't, set
+ // the display projection.
+ if (this.displayProjection && !control.displayProjection) {
+ control.displayProjection = this.displayProjection;
+ }
+
+ control.setMap(this);
+ var div = control.draw(px);
+ if (div) {
+ if(!control.outsideViewport) {
+ div.style.zIndex = this.Z_INDEX_BASE['Control'] +
+ this.controls.length;
+ this.viewPortDiv.appendChild( div );
+ }
+ }
+ if(control.autoActivate) {
+ control.activate();
+ }
+ },
+
+ /**
+ * APIMethod: getControl
+ *
+ * Parameters:
+ * id - {String} ID of the control to return.
+ *
+ * Returns:
+ * {<OpenLayers.Control>} The control from the map's list of controls
+ * which has a matching 'id'. If none found,
+ * returns null.
+ */
+ getControl: function (id) {
+ var returnControl = null;
+ for(var i=0, len=this.controls.length; i<len; i++) {
+ var control = this.controls[i];
+ if (control.id == id) {
+ returnControl = control;
+ break;
+ }
+ }
+ return returnControl;
+ },
+
+ /**
+ * APIMethod: removeControl
+ * Remove a control from the map. Removes the control both from the map
+ * object's internal array of controls, as well as from the map's
+ * viewPort (assuming the control was not added outsideViewport)
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control to remove.
+ */
+ removeControl: function (control) {
+ //make sure control is non-null and actually part of our map
+ if ( (control) && (control == this.getControl(control.id)) ) {
+ if (control.div && (control.div.parentNode == this.viewPortDiv)) {
+ this.viewPortDiv.removeChild(control.div);
+ }
+ OpenLayers.Util.removeItem(this.controls, control);
+ }
+ },
+
+ /********************************************************/
+ /* */
+ /* Popup Functions */
+ /* */
+ /* The following functions deal with adding and */
+ /* removing Popups to and from the Map */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: addPopup
+ *
+ * Parameters:
+ * popup - {<OpenLayers.Popup>}
+ * exclusive - {Boolean} If true, closes all other popups first
+ */
+ addPopup: function(popup, exclusive) {
+
+ if (exclusive) {
+ //remove all other popups from screen
+ for (var i = this.popups.length - 1; i >= 0; --i) {
+ this.removePopup(this.popups[i]);
+ }
+ }
+
+ popup.map = this;
+ this.popups.push(popup);
+ var popupDiv = popup.draw();
+ if (popupDiv) {
+ popupDiv.style.zIndex = this.Z_INDEX_BASE['Popup'] +
+ this.popups.length;
+ this.layerContainerDiv.appendChild(popupDiv);
+ }
+ },
+
+ /**
+ * APIMethod: removePopup
+ *
+ * Parameters:
+ * popup - {<OpenLayers.Popup>}
+ */
+ removePopup: function(popup) {
+ OpenLayers.Util.removeItem(this.popups, popup);
+ if (popup.div) {
+ try { this.layerContainerDiv.removeChild(popup.div); }
+ catch (e) { } // Popups sometimes apparently get disconnected
+ // from the layerContainerDiv, and cause complaints.
+ }
+ popup.map = null;
+ },
+
+ /********************************************************/
+ /* */
+ /* Container Div Functions */
+ /* */
+ /* The following functions deal with the access to */
+ /* and maintenance of the size of the container div */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: getSize
+ *
+ * Returns:
+ * {<OpenLayers.Size>} An <OpenLayers.Size> object that represents the
+ * size, in pixels, of the div into which OpenLayers
+ * has been loaded.
+ * Note - A clone() of this locally cached variable is
+ * returned, so as not to allow users to modify it.
+ */
+ getSize: function () {
+ var size = null;
+ if (this.size != null) {
+ size = this.size.clone();
+ }
+ return size;
+ },
+
+ /**
+ * APIMethod: updateSize
+ * This function should be called by any external code which dynamically
+ * changes the size of the map div (because mozilla wont let us catch
+ * the "onresize" for an element)
+ */
+ updateSize: function() {
+ // the div might have moved on the page, also
+ 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<len; i++) {
+ this.layers[i].onMapResize();
+ }
+
+ var center = this.getCachedCenter();
+
+ if (this.baseLayer != null && center != null) {
+ var zoom = this.getZoom();
+ this.zoom = null;
+ this.setCenter(center, zoom);
+ }
+
+ }
+ }
+ this.events.triggerEvent("updatesize");
+ },
+
+ /**
+ * Method: getCurrentSize
+ *
+ * Returns:
+ * {<OpenLayers.Size>} A new <OpenLayers.Size> object with the dimensions
+ * of the map div
+ */
+ getCurrentSize: function() {
+
+ var size = new OpenLayers.Size(this.div.clientWidth,
+ this.div.clientHeight);
+
+ 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 - {<OpenLayers.LonLat>} Default is this.getCenter()
+ * resolution - {float} Default is this.getResolution()
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A bounds based on resolution, center, and
+ * current mapsize.
+ */
+ calculateBounds: function(center, resolution) {
+
+ var extent = null;
+
+ if (center == null) {
+ center = this.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:
+ * {<OpenLayers.LonLat>}
+ */
+ getCenter: function () {
+ var center = null;
+ var cachedCenter = this.getCachedCenter();
+ if (cachedCenter) {
+ center = cachedCenter.clone();
+ }
+ return center;
+ },
+
+ /**
+ * Method: getCachedCenter
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>}
+ */
+ 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 - {<OpenLayers.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 - {<OpenLayers.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<len; ++i) {
+ layer = this.layers[i];
+ if (layer.visibility &&
+ (layer === this.baseLayer || layer.inRange)) {
+ layer.moveByPx(dx, dy);
+ layer.events.triggerEvent("move");
+ }
+ }
+ this.events.triggerEvent("move");
+ }
+ },
+
+ /**
+ * Method: adjustZoom
+ *
+ * Parameters:
+ * zoom - {Number} The zoom level to adjust
+ *
+ * Returns:
+ * {Integer} Adjusted zoom level that shows a map not wider than its
+ * <baseLayer>'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<ii; ++i) {
+ if (resolutions[i] <= maxResolution) {
+ zoom = i;
+ break;
+ }
+ }
+ }
+ }
+ }
+ return zoom;
+ },
+
+ /**
+ * APIMethod: getMinZoom
+ * Returns the minimum zoom level for the current map view. If the base
+ * layer is configured with <wrapDateLine> 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
+ * <baseLayer>'s maxExtent. This is an Integer value, unless the map is
+ * configured with <fractionalZoom> set to true.
+ */
+ getMinZoom: function() {
+ return this.adjustZoom(0);
+ },
+
+ /**
+ * Method: moveTo
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.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<len; i++) {
+ this.popups[i].updatePosition();
+ }
+ this.events.triggerEvent("zoomend");
+ }
+ }
+ },
+
+ /**
+ * Method: centerLayerContainer
+ * This function takes care to recenter the layerContainerDiv.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ */
+ centerLayerContainer: function (lonlat) {
+ var originPx = this.getViewPortPxFromLonLat(this.layerContainerOrigin);
+ var newPx = this.getViewPortPxFromLonLat(lonlat);
+
+ if ((originPx != null) && (newPx != null)) {
+ 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 - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {Boolean} Whether or not the lonlat passed in is non-null and within
+ * the maxExtent bounds
+ */
+ isValidLonLat: function(lonlat) {
+ var valid = false;
+ if (lonlat != null) {
+ var maxExtent = this.getMaxExtent();
+ 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:
+ * {<OpenLayers.Projection>} The Projection of the base layer.
+ */
+ getProjectionObject: function() {
+ var projection = null;
+ if (this.baseLayer != null) {
+ projection = this.baseLayer.projection;
+ }
+ return projection;
+ },
+
+ /**
+ * APIMethod: getMaxResolution
+ *
+ * Returns:
+ * {String} The Map's Maximum Resolution
+ */
+ getMaxResolution: function() {
+ var maxResolution = null;
+ if (this.baseLayer != null) {
+ maxResolution = this.baseLayer.maxResolution;
+ }
+ return maxResolution;
+ },
+
+ /**
+ * APIMethod: getMaxExtent
+ *
+ * Parameters:
+ * options - {Object}
+ *
+ * Allowed Options:
+ * restricted - {Boolean} If true, returns restricted extent (if it is
+ * available.)
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} The maxExtent property as set on the current
+ * baselayer, unless the 'restricted' option is set, in which case
+ * the 'restrictedExtent' option from the map is returned (if it
+ * is set).
+ */
+ getMaxExtent: function (options) {
+ var maxExtent = null;
+ if(options && options.restricted && this.restrictedExtent){
+ maxExtent = this.restrictedExtent;
+ } else if (this.baseLayer != null) {
+ maxExtent = this.baseLayer.maxExtent;
+ }
+ return maxExtent;
+ },
+
+ /**
+ * APIMethod: getNumZoomLevels
+ *
+ * Returns:
+ * {Integer} The total number of zoom levels that can be displayed by the
+ * current baseLayer.
+ */
+ getNumZoomLevels: function() {
+ var numZoomLevels = null;
+ if (this.baseLayer != null) {
+ numZoomLevels = this.baseLayer.numZoomLevels;
+ }
+ return numZoomLevels;
+ },
+
+ /********************************************************/
+ /* */
+ /* Baselayer Functions */
+ /* */
+ /* The following functions, all publicly exposed */
+ /* in the API?, are all merely wrappers to the */
+ /* the same calls on whatever layer is set as */
+ /* the current base layer */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: getExtent
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A Bounds object which represents the lon/lat
+ * bounds of the current viewPort.
+ * If no baselayer is set, returns null.
+ */
+ getExtent: function () {
+ var extent = null;
+ if (this.baseLayer != null) {
+ extent = this.baseLayer.getExtent();
+ }
+ return extent;
+ },
+
+ /**
+ * APIMethod: getResolution
+ *
+ * Returns:
+ * {Float} The current resolution of the map.
+ * If no baselayer is set, returns null.
+ */
+ getResolution: function () {
+ var resolution = null;
+ if (this.baseLayer != null) {
+ resolution = this.baseLayer.getResolution();
+ } 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 - {<OpenLayers.Bounds>}
+ * closest - {Boolean} Find the zoom level that most closely fits the
+ * specified bounds. Note that this may result in a zoom that does
+ * not exactly contain the entire extent.
+ * Default is false.
+ *
+ * Returns:
+ * {Integer} A suitable zoom level for the specified bounds.
+ * If no baselayer is set, returns null.
+ */
+ getZoomForExtent: function (bounds, closest) {
+ var zoom = null;
+ if (this.baseLayer != null) {
+ zoom = this.baseLayer.getZoomForExtent(bounds, closest);
+ }
+ return zoom;
+ },
+
+ /**
+ * APIMethod: getResolutionForZoom
+ *
+ * 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
+ * <setCenter> without a lonlat argument.
+ *
+ * Parameters:
+ * zoom - {Integer}
+ */
+ zoomTo: function(zoom, xy) {
+ // non-API arguments:
+ // xy - {<OpenLayers.Pixel>} 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 - {<OpenLayers.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 - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or
+ * an object with a 'x'
+ * and 'y' properties.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in view
+ * port <OpenLayers.Pixel>, translated into lon/lat
+ * by the current base layer.
+ */
+ getLonLatFromViewPortPx: function (viewPortPx) {
+ var lonlat = null;
+ if (this.baseLayer != null) {
+ lonlat = this.baseLayer.getLonLatFromViewPortPx(viewPortPx);
+ }
+ return lonlat;
+ },
+
+ /**
+ * APIMethod: getViewPortPxFromLonLat
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in
+ * <OpenLayers.LonLat>, translated into view port
+ * pixels by the current base layer.
+ */
+ getViewPortPxFromLonLat: function (lonlat) {
+ var px = null;
+ if (this.baseLayer != null) {
+ px = this.baseLayer.getViewPortPxFromLonLat(lonlat);
+ }
+ return px;
+ },
+
+ /**
+ * Method: getZoomTargetCenter
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} The zoom origin pixel location on the screen
+ * resolution - {Float} The resolution we want to get the center for
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} 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 - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} An OpenLayers.LonLat corresponding to the given
+ * OpenLayers.Pixel, translated into lon/lat by the
+ * current base layer
+ */
+ getLonLatFromPixel: function (px) {
+ return this.getLonLatFromViewPortPx(px);
+ },
+
+ /**
+ * APIMethod: getPixelFromLonLat
+ * Returns a pixel location given a map location. The map location is
+ * translated to an integer pixel location (in viewport pixel
+ * coordinates) by the current base layer.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>} A map location.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An OpenLayers.Pixel corresponding to the
+ * <OpenLayers.LonLat> translated into view port pixels by the current
+ * base layer.
+ */
+ getPixelFromLonLat: function (lonlat) {
+ var px = this.getViewPortPxFromLonLat(lonlat);
+ px.x = Math.round(px.x);
+ px.y = Math.round(px.y);
+ return px;
+ },
+
+ /**
+ * Method: getGeodesicPixelSize
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>} The pixel to get the geodesic length for. If
+ * not provided, the center pixel of the map viewport will be used.
+ *
+ * Returns:
+ * {<OpenLayers.Size>} 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 - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} 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 - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} 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 - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>}
+ */
+ getLonLatFromLayerPx: function (px) {
+ //adjust for displacement of layerContainerDiv
+ px = this.getViewPortPxFromLayerPx(px);
+ return this.getLonLatFromViewPortPx(px);
+ },
+
+ /**
+ * APIMethod: getLayerPxFromLonLat
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>} lonlat
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in
+ * <OpenLayers.LonLat>, translated into layer pixels
+ * by the current base layer
+ */
+ getLayerPxFromLonLat: function (lonlat) {
+ //adjust for displacement of layerContainerDiv
+ var px = this.getPixelFromLonLat(lonlat);
+ return this.getLayerPxFromViewPortPx(px);
+ },
+
+ /**
+ * Method: applyTransform
+ * Applies the given transform to the <layerContainerDiv>. 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 <layerContainerOriginPx>
+ * y - {Number} y parameter for the translation. Defaults to the y value of
+ * the map's <layerContainerOriginPx>
+ * scale - {Number} scale. Defaults to 1 if not provided.
+ */
+ applyTransform: function(x, y, scale) {
+ scale = scale || 1;
+ var origin = this.layerContainerOriginPx,
+ needTransform = scale !== 1;
+ x = x || origin.x;
+ y = y || origin.y;
+
+ var style = this.layerContainerDiv.style,
+ transform = this.applyTransform.transform,
+ template = this.applyTransform.template;
+
+ if (transform === undefined) {
+ transform = OpenLayers.Util.vendorPrefix.style('transform');
+ this.applyTransform.transform = transform;
+ if (transform) {
+ // Try translate3d, but only if the viewPortDiv has a transform
+ // defined in a stylesheet
+ var computedStyle = OpenLayers.Element.getStyle(this.viewPortDiv,
+ OpenLayers.Util.vendorPrefix.css('transform'));
+ if (!computedStyle || computedStyle !== 'none') {
+ template = ['translate3d(', ',0) ', 'scale3d(', ',1)'];
+ style[transform] = [template[0], '0,0', template[1]].join('');
+ }
+ // If no transform is defined in the stylesheet or translate3d
+ // does not stick, use translate and scale
+ if (!template || !~style[transform].indexOf(template[0])) {
+ template = ['translate(', ') ', 'scale(', ')'];
+ }
+ this.applyTransform.template = template;
+ }
+ }
+
+ // If we do 3d transforms, we always want to use them. If we do 2d
+ // transforms, we only use them when we need to.
+ if (transform !== null && (template[0] === 'translate3d(' || needTransform === true)) {
+ // Our 2d transforms are combined with style.left and style.top, so
+ // adjust x and y values and set the origin as left and top
+ if (needTransform === true && template[0] === 'translate(') {
+ x -= origin.x;
+ y -= origin.y;
+ style.left = origin.x + 'px';
+ style.top = origin.y + 'px';
+ }
+ style[transform] = [
+ template[0], x, 'px,', y, 'px', template[1],
+ template[2], scale, ',', scale, template[3]
+ ].join('');
+ } else {
+ style.left = x + 'px';
+ style.top = y + 'px';
+ // We previously might have had needTransform, so remove transform
+ if (transform !== null) {
+ style[transform] = '';
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Map"
+});
+
+/**
+ * Constant: TILE_WIDTH
+ * {Integer} 256 Default tile width (unless otherwise specified)
+ */
+OpenLayers.Map.TILE_WIDTH = 256;
+/**
+ * Constant: TILE_HEIGHT
+ * {Integer} 256 Default tile height (unless otherwise specified)
+ */
+OpenLayers.Map.TILE_HEIGHT = 256;
+/* ======================================================================
+ OpenLayers/Layer.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Map.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Layer
+ */
+OpenLayers.Layer = OpenLayers.Class({
+
+ /**
+ * APIProperty: id
+ * {String}
+ */
+ id: null,
+
+ /**
+ * APIProperty: name
+ * {String}
+ */
+ name: null,
+
+ /**
+ * APIProperty: div
+ * {DOMElement}
+ */
+ div: null,
+
+ /**
+ * APIProperty: opacity
+ * {Float} The layer's opacity. Float number between 0.0 and 1.0. Default
+ * is 1.
+ */
+ opacity: 1,
+
+ /**
+ * APIProperty: alwaysInRange
+ * {Boolean} If a layer's display should not be scale-based, this should
+ * be set to true. This will cause the layer, as an overlay, to always
+ * be 'active', by always returning true from the calculateInRange()
+ * function.
+ *
+ * If not explicitly specified for a layer, its value will be
+ * determined on startup in initResolutions() based on whether or not
+ * any scale-specific properties have been set as options on the
+ * layer. If no scale-specific options have been set on the layer, we
+ * assume that it should always be in range.
+ *
+ * See #987 for more info.
+ */
+ alwaysInRange: null,
+
+ /**
+ * Constant: RESOLUTION_PROPERTIES
+ * {Array} The properties that are used for calculating resolutions
+ * information.
+ */
+ RESOLUTION_PROPERTIES: [
+ 'scales', 'resolutions',
+ 'maxScale', 'minScale',
+ 'maxResolution', 'minResolution',
+ 'numZoomLevels', 'maxZoomLevel'
+ ],
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.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 <calculateInRange>). 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
+ * {<OpenLayers.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
+ * <OpenLayers.Control.Attribution> 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 <OpenLayers.Map.setCenter> whenever the zoom
+ * changes.
+ */
+ inRange: false,
+
+ /**
+ * Propery: imageSize
+ * {<OpenLayers.Size>} 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 <OpenLayers.Events.on>. 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
+ * {<OpenLayers.Projection>} or {<String>} 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 <OpenLayers.Projection> 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 <resolutions> 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
+ * {<OpenLayers.Bounds>|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
+ * <displayOutsideMaxExtent> is set to false, data will not be
+ * requested that falls completely outside of these bounds.
+ */
+ maxExtent: null,
+
+ /**
+ * APIProperty: minExtent
+ * {<OpenLayers.Bounds>|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 <OpenLayers.Map.tileSize>
+ * 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 - {<OpenLayers.Layer>} The layer to be cloned
+ *
+ * Returns:
+ * {<OpenLayers.Layer>} An exact clone of this <OpenLayers.Layer>
+ */
+ 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 <options> 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 - {<OpenLayers.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 - {<OpenLayers.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 - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ //to be overridden by subclasses
+ },
+
+ /**
+ * APIMethod: getImageSize
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.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:
+ * {<OpenLayers.Size>} 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 - {<OpenLayers.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<len; i++) {
+ p = this.RESOLUTION_PROPERTIES[i];
+ props[p] = this.options[p];
+ if(alwaysInRange && this.options[p]) {
+ alwaysInRange = false;
+ }
+ }
+ if(this.options.alwaysInRange == null) {
+ this.alwaysInRange = alwaysInRange;
+ }
+
+ // if we don't have resolutions then attempt to derive them from scales
+ if(props.resolutions == null) {
+ props.resolutions = this.resolutionsFromScales(props.scales);
+ }
+
+ // if we still don't have resolutions then attempt to calculate them
+ if(props.resolutions == null) {
+ props.resolutions = this.calculateResolutions(props);
+ }
+
+ // if we couldn't calculate resolutions then we look at we have
+ // in the map
+ if(props.resolutions == null) {
+ for(i=0, len=this.RESOLUTION_PROPERTIES.length; i<len; i++) {
+ p = this.RESOLUTION_PROPERTIES[i];
+ props[p] = this.options[p] != null ?
+ this.options[p] : this.map[p];
+ }
+ if(props.resolutions == null) {
+ props.resolutions = this.resolutionsFromScales(props.scales);
+ }
+ if(props.resolutions == null) {
+ props.resolutions = this.calculateResolutions(props);
+ }
+ }
+
+ // ok, we new need to set properties in the instance
+
+ // get maxResolution from the config if it's defined there
+ var maxResolution;
+ if(this.options.maxResolution &&
+ this.options.maxResolution !== "auto") {
+ maxResolution = this.options.maxResolution;
+ }
+ if(this.options.minScale) {
+ maxResolution = OpenLayers.Util.getResolutionFromScale(
+ this.options.minScale, this.units);
+ }
+
+ // get minResolution from the config if it's defined there
+ var minResolution;
+ if(this.options.minResolution &&
+ this.options.minResolution !== "auto") {
+ minResolution = this.options.minResolution;
+ }
+ if(this.options.maxScale) {
+ minResolution = OpenLayers.Util.getResolutionFromScale(
+ this.options.maxScale, this.units);
+ }
+
+ if(props.resolutions) {
+
+ //sort resolutions array descendingly
+ props.resolutions.sort(function(a, b) {
+ return (b - a);
+ });
+
+ // if we still don't have a maxResolution get it from the
+ // resolutions array
+ if(!maxResolution) {
+ maxResolution = props.resolutions[0];
+ }
+
+ // if we still don't have a minResolution get it from the
+ // resolutions array
+ if(!minResolution) {
+ var lastIdx = props.resolutions.length - 1;
+ minResolution = props.resolutions[lastIdx];
+ }
+ }
+
+ this.resolutions = props.resolutions;
+ if(this.resolutions) {
+ len = this.resolutions.length;
+ this.scales = new Array(len);
+ for(i=0; i<len; i++) {
+ this.scales[i] = OpenLayers.Util.getScaleFromResolution(
+ this.resolutions[i], this.units);
+ }
+ this.numZoomLevels = len;
+ }
+ this.minResolution = minResolution;
+ if(minResolution) {
+ this.maxScale = OpenLayers.Util.getScaleFromResolution(
+ minResolution, this.units);
+ }
+ this.maxResolution = maxResolution;
+ if(maxResolution) {
+ this.minScale = OpenLayers.Util.getScaleFromResolution(
+ maxResolution, this.units);
+ }
+ },
+
+ /**
+ * Method: resolutionsFromScales
+ * Derive resolutions from scales.
+ *
+ * Parameters:
+ * scales - {Array(Number)} Scales
+ *
+ * Returns
+ * {Array(Number)} Resolutions
+ */
+ resolutionsFromScales: function(scales) {
+ if(scales == null) {
+ return;
+ }
+ var resolutions, i, len;
+ len = scales.length;
+ resolutions = new Array(len);
+ for(i=0; i<len; i++) {
+ resolutions[i] = OpenLayers.Util.getResolutionFromScale(
+ scales[i], this.units);
+ }
+ return resolutions;
+ },
+
+ /**
+ * Method: calculateResolutions
+ * Calculate resolutions based on the provided properties.
+ *
+ * Parameters:
+ * props - {Object} Properties
+ *
+ * Returns:
+ * {Array({Number})} Array of resolutions.
+ */
+ calculateResolutions: function(props) {
+
+ var viewSize, wRes, hRes;
+
+ // determine maxResolution
+ var maxResolution = props.maxResolution;
+ if(props.minScale != null) {
+ maxResolution =
+ OpenLayers.Util.getResolutionFromScale(props.minScale,
+ this.units);
+ } else if(maxResolution == "auto" && this.maxExtent != null) {
+ viewSize = this.map.getSize();
+ wRes = this.maxExtent.getWidth() / viewSize.w;
+ hRes = this.maxExtent.getHeight() / viewSize.h;
+ maxResolution = Math.max(wRes, hRes);
+ }
+
+ // determine minResolution
+ var minResolution = props.minResolution;
+ if(props.maxScale != null) {
+ minResolution =
+ OpenLayers.Util.getResolutionFromScale(props.maxScale,
+ this.units);
+ } else if(props.minResolution == "auto" && this.minExtent != null) {
+ viewSize = this.map.getSize();
+ wRes = this.minExtent.getWidth() / viewSize.w;
+ hRes = this.minExtent.getHeight()/ viewSize.h;
+ minResolution = Math.max(wRes, hRes);
+ }
+
+ if(typeof maxResolution !== "number" &&
+ typeof minResolution !== "number" &&
+ this.maxExtent != null) {
+ // maxResolution for default grid sets assumes that at zoom
+ // level zero, the whole world fits on one tile.
+ var tileSize = this.map.getTileSize();
+ maxResolution = Math.max(
+ this.maxExtent.getWidth() / tileSize.w,
+ this.maxExtent.getHeight() / tileSize.h
+ );
+ }
+
+ // determine numZoomLevels
+ var maxZoomLevel = props.maxZoomLevel;
+ var numZoomLevels = props.numZoomLevels;
+ if(typeof minResolution === "number" &&
+ typeof maxResolution === "number" && numZoomLevels === undefined) {
+ var ratio = maxResolution / minResolution;
+ numZoomLevels = Math.floor(Math.log(ratio) / Math.log(2)) + 1;
+ } else if(numZoomLevels === undefined && maxZoomLevel != null) {
+ numZoomLevels = maxZoomLevel + 1;
+ }
+
+ // are we able to calculate resolutions?
+ if(typeof numZoomLevels !== "number" || numZoomLevels <= 0 ||
+ (typeof maxResolution !== "number" &&
+ typeof minResolution !== "number")) {
+ return;
+ }
+
+ // now we have numZoomLevels and at least one of maxResolution
+ // or minResolution, we can populate the resolutions array
+
+ var resolutions = new Array(numZoomLevels);
+ var base = 2;
+ if(typeof minResolution == "number" &&
+ typeof maxResolution == "number") {
+ // if maxResolution and minResolution are set, we calculate
+ // the base for exponential scaling that starts at
+ // maxResolution and ends at minResolution in numZoomLevels
+ // steps.
+ base = Math.pow(
+ (maxResolution / minResolution),
+ (1 / (numZoomLevels - 1))
+ );
+ }
+
+ var i;
+ if(typeof maxResolution === "number") {
+ for(i=0; i<numZoomLevels; i++) {
+ resolutions[i] = maxResolution / Math.pow(base, i);
+ }
+ } else {
+ for(i=0; i<numZoomLevels; i++) {
+ resolutions[numZoomLevels - 1 - i] =
+ minResolution * Math.pow(base, i);
+ }
+ }
+
+ return resolutions;
+ },
+
+ /**
+ * APIMethod: getResolution
+ *
+ * Returns:
+ * {Float} The currently selected resolution of the map, taken from the
+ * resolutions array, indexed by current zoom level.
+ */
+ getResolution: function() {
+ var zoom = this.map.getZoom();
+ return this.getResolutionForZoom(zoom);
+ },
+
+ /**
+ * APIMethod: getExtent
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} 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 - {<OpenLayers.Bounds>}
+ * closest - {Boolean} Find the zoom level that most closely fits the
+ * specified bounds. Note that this may result in a zoom that does
+ * not exactly contain the entire extent.
+ * Default is false.
+ *
+ * Returns:
+ * {Integer} 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:
+ * {<OpenLayers.Bounds>}
+ */
+ 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<len; ++i) {
+ res = this.resolutions[i];
+ if(res >= 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<len; i++) {
+ if (closest) {
+ diff = Math.abs(this.resolutions[i] - resolution);
+ if (diff > minDiff) {
+ break;
+ }
+ minDiff = diff;
+ } else {
+ if (this.resolutions[i] < resolution) {
+ break;
+ }
+ }
+ }
+ zoom = Math.max(0, i-1);
+ }
+ return zoom;
+ },
+
+ /**
+ * APIMethod: getLonLatFromViewPortPx
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or
+ * an object with a 'x'
+ * and 'y' properties.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in
+ * view port <OpenLayers.Pixel>, 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 - {<OpenLayers.LonLat>|Object} An OpenLayers.LonLat or
+ * an object with a 'lon'
+ * and 'lat' properties.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An <OpenLayers.Pixel> 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 - {<OpenLayers.Bounds>}
+ */
+ adjustBounds: function (bounds) {
+
+ if (this.gutter) {
+ // Adjust the extent of a bounds in map units by the
+ // layer's gutter in pixels.
+ var mapGutter = this.gutter * this.map.getResolution();
+ bounds = new OpenLayers.Bounds(bounds.left - mapGutter,
+ bounds.bottom - mapGutter,
+ bounds.right + mapGutter,
+ bounds.top + mapGutter);
+ }
+
+ if (this.wrapDateLine) {
+ // wrap around the date line, within the limits of rounding error
+ var wrappingOptions = {
+ 'rightTolerance':this.getResolution(),
+ 'leftTolerance':this.getResolution()
+ };
+ bounds = bounds.wrapDateLine(this.maxExtent, wrappingOptions);
+
+ }
+ return bounds;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer"
+});
+/* ======================================================================
+ OpenLayers/Layer/HTTPRequest.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.HTTPRequest
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer>
+ */
+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:
+ * {<OpenLayers.Layer.HTTPRequest>} An exact clone of this
+ * <OpenLayers.Layer.HTTPRequest>
+ */
+ 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<len; i++) {
+ product *= paramString.charCodeAt(i) * this.URL_HASH_FACTOR;
+ product -= Math.floor(product);
+ }
+ return urls[Math.floor(product * urls.length)];
+ },
+
+ /**
+ * Method: getFullRequestString
+ * Combine url with layer's params and these newParams.
+ *
+ * does checking on the serverPath variable, allowing for cases when it
+ * is supplied with trailing ? or &, as well as cases where not.
+ *
+ * return in formatted string like this:
+ * "server?key1=value1&key2=value2&key3=value3"
+ *
+ * WARNING: The altUrl parameter is deprecated and will be removed in 3.0.
+ *
+ * Parameters:
+ * newParams - {Object}
+ * altUrl - {String} Use this as the url instead of the layer's url
+ *
+ * Returns:
+ * {String}
+ */
+ getFullRequestString:function(newParams, altUrl) {
+
+ // if not altUrl passed in, use layer's url
+ var url = altUrl || this.url;
+
+ // create a new params hashtable with all the layer params and the
+ // new params together. then convert to string
+ var allParams = OpenLayers.Util.extend({}, this.params);
+ allParams = OpenLayers.Util.extend(allParams, newParams);
+ var paramsString = OpenLayers.Util.getParameterString(allParams);
+
+ // if url is not a string, it should be an array of strings,
+ // in which case we will deterministically select one of them in
+ // order to evenly distribute requests to different urls.
+ //
+ if (OpenLayers.Util.isArray(url)) {
+ url = this.selectUrl(paramsString, url);
+ }
+
+ // ignore parameters that are already in the url search string
+ var urlParams =
+ OpenLayers.Util.upperCaseObject(OpenLayers.Util.getParameters(url));
+ for(var key in allParams) {
+ if(key.toUpperCase() in urlParams) {
+ delete allParams[key];
+ }
+ }
+ paramsString = OpenLayers.Util.getParameterString(allParams);
+
+ return OpenLayers.Util.urlAppend(url, paramsString);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.HTTPRequest"
+});
+/* ======================================================================
+ OpenLayers/Tile.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.Tile
+ * This is a class designed to designate a single tile, however
+ * it is explicitly designed to do relatively little. Tiles store
+ * information about themselves -- such as the URL that they are related
+ * to, and their size - but do not add themselves to the layer div
+ * automatically, for example. Create a new tile with the
+ * <OpenLayers.Tile> constructor, or a subclass.
+ *
+ * TBD 3.0 - remove reference to url in above paragraph
+ *
+ */
+OpenLayers.Tile = OpenLayers.Class({
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.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 <draw>(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 <OpenLayers.Events.on>. 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
+ * <OpenLayers.Layer.Grid>. 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
+ * {<OpenLayers.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
+ * {<OpenLayers.Bounds>} null
+ */
+ bounds: null,
+
+ /**
+ * Property: size
+ * {<OpenLayers.Size>} null
+ */
+ size: null,
+
+ /**
+ * Property: position
+ * {<OpenLayers.Pixel>} 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 <OpenLayers.Tile> instance.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>} layer that the tile will go in.
+ * position - {<OpenLayers.Pixel>}
+ * bounds - {<OpenLayers.Bounds>}
+ * url - {<String>}
+ * size - {<OpenLayers.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 <clear> and return the result from <shouldDraw>.
+ *
+ * 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 {<OpenLayers.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 - {<OpenLayers.Bounds>}
+ * position - {<OpenLayers.Pixel>}
+ * 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
+ * <OpenLayers.Tile.Image> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Tile>
+ */
+OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.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 <OpenLayers.Tile> events):
+ * beforeload - Triggered before an image is prepared for loading, when the
+ * url for the image is known already. Listeners may call <setImage> 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 <getCanvasContext> 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 <OpenLayers.Tile.Image> instance.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>} layer that the tile will go in.
+ * position - {<OpenLayers.Pixel>}
+ * bounds - {<OpenLayers.Bounds>}
+ * url - {<String>} Deprecated. Remove me in 3.0.
+ * size - {<OpenLayers.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 <onImageLoad> 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 <OpenLayers.Layer.Grid> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.HTTPRequest>
+ */
+OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
+
+ /**
+ * APIProperty: tileSize
+ * {<OpenLayers.Size>}
+ */
+ tileSize: null,
+
+ /**
+ * Property: tileOriginCorner
+ * {String} If the <tileOrigin> property is not provided, the tile origin
+ * will be derived from the layer's <maxExtent>. The corner of the
+ * <maxExtent> 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
+ * {<OpenLayers.LonLat>} 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
+ * <maxExtent>. Default is ``null``.
+ */
+ tileOrigin: null,
+
+ /** APIProperty: tileOptions
+ * {Object} optional configuration options for <OpenLayers.Tile> instances
+ * created by this Layer, if supported by the tile class.
+ */
+ tileOptions: null,
+
+ /**
+ * APIProperty: tileClass
+ * {<OpenLayers.Tile>} The tile class to use for this layer.
+ * Defaults is OpenLayers.Tile.Image.
+ */
+ tileClass: OpenLayers.Tile.Image,
+
+ /**
+ * Property: grid
+ * {Array(Array(<OpenLayers.Tile>))} 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 <singleTile> layers,
+ * 2500 for tiled layers. See <className> 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 <singleTile>),
+ * 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, <removeBackBufferDelay>
+ * 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 - {<OpenLayers.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 - {<OpenLayers.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<len; iRow++) {
+ var row = this.grid[iRow];
+ for(var iCol=0, clen=row.length; iCol<clen; iCol++) {
+ var tile = row[iCol];
+ this.destroyTile(tile);
+ }
+ }
+ this.grid = [];
+ this.gridResolution = null;
+ this.gridLayout = null;
+ }
+ },
+
+ /**
+ * 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) {
+ var singleTileChanged = newOptions.singleTile !== undefined &&
+ newOptions.singleTile !== this.singleTile;
+ OpenLayers.Layer.HTTPRequest.prototype.addOptions.apply(this, arguments);
+ if (this.map && singleTileChanged) {
+ this.initProperties();
+ this.clearGrid();
+ this.tileSize = this.options.tileSize;
+ this.setTileSize();
+ this.moveTo(null, true);
+ }
+ },
+
+ /**
+ * APIMethod: clone
+ * Create a clone of this layer
+ *
+ * Parameters:
+ * obj - {Object} Is this ever used?
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Grid>} 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 - {<OpenLayers.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 - {<OpenLayers.LonLat>} map location
+ *
+ * Returns:
+ * {Object} Object with the following properties: tile ({<OpenLayers.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 - {<OpenLayers.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 <serverResolutions> and <zoomOffset>.
+ *
+ * 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<lenI; i++) {
+ for(var j=0, lenJ=this.grid[i].length; j<lenJ; j++) {
+ var tile = this.grid[i][j],
+ markup = this.grid[i][j].createBackBuffer();
+ if (markup) {
+ markup._i = i;
+ markup._j = j;
+ markup._w = tile.size.w;
+ markup._h = tile.size.h;
+ markup.id = tile.id + '_bb';
+ backBuffer.appendChild(markup);
+ }
+ }
+ }
+ }
+ return backBuffer;
+ },
+
+ /**
+ * Method: removeBackBuffer
+ * Remove back buffer from DOM.
+ */
+ removeBackBuffer: function() {
+ if (this._transitionElement) {
+ for (var i=this.transitionendEvents.length-1; 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 - {<OpenLayers.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:
+ * {<OpenLayers.Bounds>} 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 - {<OpenLayers.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 - {<OpenLayers.Bound>|Object} OpenLayers.Bounds or an
+ * object with a 'left' and 'top' properties.
+ * origin - {<OpenLayers.LonLat>|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 <tileOrigin>
+ * property is supplied, that will be returned. Otherwise, the origin
+ * will be derived from the layer's <maxExtent> property. In this case,
+ * the tile origin will be the corner of the <maxExtent> given by the
+ * <tileOriginCorner> property.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} 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:
+ * {<OpenLayers.Bounds>} 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 - {<OpenLayers.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<ii; ++i) {
+ tileData[i].tile.draw();
+ }
+ },
+
+ /**
+ * Method: getMaxExtent
+ * Get this layer's maximum extent. (Implemented as a getter for
+ * potential specific implementations in sub-classes.)
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}
+ */
+ getMaxExtent: function() {
+ return this.maxExtent;
+ },
+
+ /**
+ * APIMethod: addTile
+ * Create a tile, initialize it, and add it to the layer div.
+ *
+ * Parameters
+ * bounds - {<OpenLayers.Bounds>}
+ * position - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.Tile>} 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 - {<OpenLayers.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 - {<OpenLayers.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<len; i++) {
+ var tile = row[i];
+ var position = modelRow[i].position.clone();
+ position.y += tileSize.h * sign;
+ tile.moveTo(this.getTileBoundsForGridIndex(rowIndex, i), position);
+ }
+ grid[prepend ? 'unshift' : 'push'](row);
+ },
+
+ /**
+ * Method: shiftColumn
+ * Shift grid work in the other dimension
+ *
+ * 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
+ */
+ shiftColumn: function(prepend, tileSize) {
+ var grid = this.grid;
+ var colIndex = prepend ? 0 : (grid[0].length - 1);
+ var sign = prepend ? -1 : 1;
+ var tileLayout = this.gridLayout;
+ tileLayout.startcol += sign;
+
+ for (var i=0, len=grid.length; i<len; i++) {
+ var row = grid[i];
+ var position = row[colIndex].position.clone();
+ var tile = row[prepend ? 'pop' : 'shift']();
+ position.x += tileSize.w * sign;
+ tile.moveTo(this.getTileBoundsForGridIndex(i, colIndex), position);
+ row[prepend ? 'unshift' : 'push'](tile);
+ }
+ },
+
+ /**
+ * Method: removeExcessTiles
+ * When the size of the map or the buffer changes, we may need to
+ * remove some excess rows and columns.
+ *
+ * Parameters:
+ * rows - {Integer} Maximum number of rows we want our grid to have.
+ * columns - {Integer} Maximum number of columns we want our grid to have.
+ */
+ removeExcessTiles: function(rows, columns) {
+ var i, l;
+
+ // remove extra rows
+ while (this.grid.length > rows) {
+ var row = this.grid.pop();
+ for (i=0, l=row.length; i<l; i++) {
+ var tile = row[i];
+ this.destroyTile(tile);
+ }
+ }
+
+ // remove extra columns
+ for (i=0, l=this.grid.length; i<l; i++) {
+ while (this.grid[i].length > 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 - {<OpenLayers.Pixel>} The location in the viewport.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} Bounds of the tile at the given pixel location.
+ */
+ getTileBounds: function(viewPortPx) {
+ var maxExtent = this.maxExtent;
+ var resolution = this.getResolution();
+ var tileMapWidth = resolution * this.tileSize.w;
+ var tileMapHeight = resolution * this.tileSize.h;
+ var mapPoint = this.getLonLatFromViewPortPx(viewPortPx);
+ var tileLeft = maxExtent.left + (tileMapWidth *
+ Math.floor((mapPoint.lon -
+ maxExtent.left) /
+ tileMapWidth));
+ var tileBottom = maxExtent.bottom + (tileMapHeight *
+ Math.floor((mapPoint.lat -
+ maxExtent.bottom) /
+ tileMapHeight));
+ return new OpenLayers.Bounds(tileLeft, tileBottom,
+ tileLeft + tileMapWidth,
+ tileBottom + tileMapHeight);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Grid"
+});
+/* ======================================================================
+ OpenLayers/Layer/XYZ.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.XYZ
+ * The XYZ class is designed to make it easier for people who have tiles
+ * arranged by a standard XYZ grid.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+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 <zoomOffset> is an alternative to
+ * setting <serverResolutions> 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) <serverResolutions> can include
+ * resolutions that the server supports and that you don't want to
+ * provide with this layer; you can also look at <zoomOffset>, which is
+ * an alternative to <serverResolutions> for that specific purpose.
+ * (b) The map can work with resolutions that aren't supported by
+ * the server, i.e. that aren't in <serverResolutions>. 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:
+ * {<OpenLayers.Layer.XYZ>} 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 - {<OpenLayers.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 - {<OpenLayers.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 - {<OpenLayers.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.XYZ>
+ */
+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: "&copy; <a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors",
+
+ /**
+ * Property: sphericalMercator
+ * {Boolean}
+ */
+ sphericalMercator: true,
+
+ /**
+ * Property: wrapDateLine
+ * {Boolean}
+ */
+ wrapDateLine: true,
+
+ /** APIProperty: tileOptions
+ * {Object} optional configuration options for <OpenLayers.Tile> 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.
+ * <OpenLayers.Layer.Grid.buffer>).
+ */
+ 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
+ * {<OpenLayers.Bounds>}
+ */
+ 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
+ * {<OpenLayers.Size>}
+ */
+ size: null,
+
+ /**
+ * Property: resolution
+ * {Float} cache of current map resolution
+ */
+ resolution: null,
+
+ /**
+ * Property: map
+ * {<OpenLayers.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
+ * <calculateFeatureDx>).
+ */
+ featureDx: 0,
+
+ /**
+ * Constructor: OpenLayers.Renderer
+ *
+ * Parameters:
+ * containerID - {<String>}
+ * 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 - {<OpenLayers.Bounds>}
+ * 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 - {<OpenLayers.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 - {<OpenLayers.Feature.Vector>}
+ * style - {<Object>}
+ *
+ * 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 - {<OpenLayers.Bounds>} Bounds of the feature
+ * worldBounds - {<OpenLayers.Bounds>} 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 - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {<String>}
+ */
+ 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 - {<OpenLayers.Geometry.Point>}
+ */
+ 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 - {<OpenLayers.Event>}
+ *
+ * Returns:
+ * {String} A feature id or undefined.
+ */
+ getFeatureIdFromEvent: function(evt) {},
+
+ /**
+ * Method: eraseFeatures
+ * This is called by the layer to erase features
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ */
+ eraseFeatures: function(features) {
+ if(!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+ for(var i=0, len=features.length; i<len; ++i) {
+ var feature = features[i];
+ this.eraseGeometry(feature.geometry, feature.id);
+ this.removeText(feature.id);
+ }
+ },
+
+ /**
+ * Method: eraseGeometry
+ * Remove a geometry from the renderer (by id).
+ * virtual function.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * 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 - {<OpenLayers.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>
+ */
+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 - {<String>}
+ * 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 - {<OpenLayers.Bounds>}
+ * 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 - {<OpenLayers.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 - {<OpenLayers.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 - {<OpenLayers.Feature.Vector>}
+ * style - {<Object>}
+ *
+ * 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 - {<OpenLayers.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 - {<OpenLayers.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 - {<OpenLayers.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<symbol.length; i=i+2) {
+ x = symbol[i];
+ y = symbol[i+1];
+ if (i == 0) this.canvas.moveTo(x,y);
+ this.canvas.lineTo(x,y);
+ }
+ this.canvas.closePath();
+ this.canvas.fill();
+
+ if (this.hitDetection) {
+ this.setHitContextStyle("fill", featureId, style);
+ this.hitContext.beginPath();
+ for (i=0; i<symbol.length; i=i+2) {
+ x = symbol[i];
+ y = symbol[i+1];
+ if (i == 0) this.canvas.moveTo(x,y);
+ this.hitContext.lineTo(x,y);
+ }
+ this.hitContext.closePath();
+ this.hitContext.fill();
+ }
+ }
+
+ if (style.stroke !== false) {
+ this.setCanvasStyle("stroke", style);
+ this.canvas.beginPath();
+ for (i=0; i<symbol.length; i=i+2) {
+ x = symbol[i];
+ y = symbol[i+1];
+ if (i == 0) this.canvas.moveTo(x,y);
+ this.canvas.lineTo(x,y);
+ }
+ this.canvas.closePath();
+ this.canvas.stroke();
+
+
+ if (this.hitDetection) {
+ this.setHitContextStyle("stroke", featureId, style, scaling);
+ this.hitContext.beginPath();
+ for (i=0; i<symbol.length; i=i+2) {
+ x = symbol[i];
+ y = symbol[i+1];
+ if (i == 0) this.hitContext.moveTo(x,y);
+ this.hitContext.lineTo(x,y);
+ }
+ this.hitContext.closePath();
+ this.hitContext.stroke();
+ }
+
+ }
+
+ style.strokeWidth = unscaledStrokeWidth;
+ this.canvas.restore();
+ if (this.hitDetection) { this.hitContext.restore(); }
+ this.setCanvasStyle("reset");
+ },
+
+ /**
+ * Method: setCanvasStyle
+ * Prepare the canvas for drawing by setting various global settings.
+ *
+ * Parameters:
+ * type - {String} one of 'stroke', 'fill', or 'reset'
+ * style - {Object} Symbolizer hash
+ */
+ setCanvasStyle: function(type, style) {
+ if (type === "fill") {
+ this.canvas.globalAlpha = style['fillOpacity'];
+ this.canvas.fillStyle = style['fillColor'];
+ } else if (type === "stroke") {
+ this.canvas.globalAlpha = style['strokeOpacity'];
+ this.canvas.strokeStyle = style['strokeColor'];
+ this.canvas.lineWidth = style['strokeWidth'];
+ } else {
+ this.canvas.globalAlpha = 0;
+ this.canvas.lineWidth = 1;
+ }
+ },
+
+ /**
+ * Method: featureIdToHex
+ * Convert a feature ID string into an RGB hex string.
+ *
+ * Parameters:
+ * featureId - {String} Feature id
+ *
+ * Returns:
+ * {String} RGB hex string.
+ */
+ featureIdToHex: function(featureId) {
+ var id = Number(featureId.split("_").pop()) + 1; // zero for no feature
+ if (id >= 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 - {<OpenLayers.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 - {<OpenLayers.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 - {<OpenLayers.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 - {<OpenLayers.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<len; ++i) {
+ var pt = this.getLocalXY(components[i]);
+ context.lineTo(pt[0], pt[1]);
+ }
+ if (type === "fill") {
+ context.fill();
+ } else {
+ context.stroke();
+ }
+ }
+ },
+
+ /**
+ * Method: drawPolygon
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * 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<len; ++i) {
+ /**
+ * Note that this is overly agressive. Here we punch holes through
+ * all previously rendered features on the same canvas. A better
+ * solution for polygons with interior rings would be to draw the
+ * polygon on a sketch canvas first. We could erase all holes
+ * there and then copy the drawing to the layer canvas.
+ * TODO: http://trac.osgeo.org/openlayers/ticket/3130
+ */
+ this.canvas.globalCompositeOperation = "destination-out";
+ if (this.hitDetection) {
+ this.hitContext.globalCompositeOperation = "destination-out";
+ }
+ this.drawLinearRing(
+ components[i],
+ OpenLayers.Util.applyDefaults({stroke: false, fillOpacity: 1.0}, style),
+ featureId
+ );
+ this.canvas.globalCompositeOperation = "source-over";
+ if (this.hitDetection) {
+ this.hitContext.globalCompositeOperation = "source-over";
+ }
+ this.drawLinearRing(
+ components[i],
+ OpenLayers.Util.applyDefaults({fill: false}, style),
+ featureId
+ );
+ }
+ },
+
+ /**
+ * Method: drawText
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * location - {<OpenLayers.Point>}
+ * 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 (<FF3.1)
+ this.canvas.mozTextStyle = fontStyle;
+ // No built-in text alignment, so we measure and adjust the position
+ var hfactor =
+ OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[0]];
+ if (hfactor == null) {
+ hfactor = -.5;
+ }
+ var vfactor =
+ OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[1]];
+ if (vfactor == null) {
+ vfactor = -.5;
+ }
+ var lineHeight = this.canvas.mozMeasureText('xx');
+ pt[1] += lineHeight*(1 + (vfactor*numRows));
+ for (var i = 0; i < numRows; i++) {
+ var x = pt[0] + (hfactor*this.canvas.mozMeasureText(labelRows[i]));
+ var y = pt[1] + (i*lineHeight);
+ this.canvas.translate(x, y);
+ this.canvas.mozDrawText(labelRows[i]);
+ this.canvas.translate(-x, -y);
+ }
+ }
+ this.setCanvasStyle("reset");
+ },
+
+ /**
+ * Method: getLocalXY
+ * transform geographic xy into pixel xy
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ */
+ 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 - {<OpenLayers.Event>}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector} A feature or undefined. This method returns a
+ * feature instead of a feature id to avoid an unnecessary lookup on the
+ * layer.
+ */
+ getFeatureIdFromEvent: function(evt) {
+ var featureId, feature;
+
+ if (this.hitDetection && this.root.style.display !== "none") {
+ // this dragging check should go in the feature handler
+ if (!this.map.dragging) {
+ var xy = evt.xy;
+ var x = xy.x | 0;
+ var y = xy.y | 0;
+ var data = this.hitContext.getImageData(x, y, 1, 1).data;
+ if (data[3] === 255) { // antialiased
+ var id = data[2] + (256 * (data[1] + (256 * data[0])));
+ if (id) {
+ featureId = "OpenLayers_Feature_Vector_" + (id - 1 + this.hitOverflow);
+ try {
+ feature = this.features[featureId][0];
+ } catch(err) {
+ // Because of antialiasing on the canvas, when the hit location is at a point where the edge of
+ // one symbol intersects the interior of another symbol, a wrong hit color (and therefore id) results.
+ // todo: set Antialiasing = 'off' on the hitContext as soon as browsers allow it.
+ }
+ }
+ }
+ }
+ }
+ return feature;
+ },
+
+ /**
+ * Method: eraseFeatures
+ * This is called by the layer to erase features; removes the feature from
+ * the list, then redraws the layer.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ */
+ eraseFeatures: function(features) {
+ if(!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+ for(var i=0; i<features.length; ++i) {
+ delete this.features[features[i].id];
+ }
+ this.redraw();
+ },
+
+ /**
+ * Method: redraw
+ * The real 'meat' of the function: any time things have changed,
+ * redraw() can be called to loop over all the data and (you guessed
+ * it) redraw it. Unlike Elements-based Renderers, we can't interact
+ * with things once they're drawn, to remove them, for example, so
+ * instead we have to just clear everything and draw from scratch.
+ */
+ redraw: function() {
+ if (!this.locked) {
+ var height = this.root.height;
+ var width = this.root.width;
+ this.canvas.clearRect(0, 0, width, height);
+ if (this.hitDetection) {
+ this.hitContext.clearRect(0, 0, width, height);
+ }
+ var labelMap = [];
+ var feature, geometry, style;
+ var worldBounds = (this.map.baseLayer && this.map.baseLayer.wrapDateLine) && this.map.getMaxExtent();
+ for (var id in this.features) {
+ if (!this.features.hasOwnProperty(id)) { continue; }
+ feature = this.features[id][0];
+ geometry = feature.geometry;
+ this.calculateFeatureDx(geometry.getBounds(), worldBounds);
+ style = this.features[id][1];
+ this.drawGeometry(geometry, style, feature.id);
+ if(style.label) {
+ labelMap.push([feature, style]);
+ }
+ }
+ var item;
+ for (var i=0, len=labelMap.length; i<len; ++i) {
+ item = labelMap[i];
+ this.drawText(item[0].geometry.getCentroid(), item[1]);
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Renderer.Canvas"
+});
+
+/**
+ * Constant: OpenLayers.Renderer.Canvas.LABEL_ALIGN
+ * {Object}
+ */
+OpenLayers.Renderer.Canvas.LABEL_ALIGN = {
+ "l": "left",
+ "r": "right",
+ "t": "top",
+ "b": "bottom"
+};
+
+/**
+ * Constant: OpenLayers.Renderer.Canvas.LABEL_FACTOR
+ * {Object}
+ */
+OpenLayers.Renderer.Canvas.LABEL_FACTOR = {
+ "l": 0,
+ "r": -1,
+ "t": 0,
+ "b": -1
+};
+
+/**
+ * Constant: OpenLayers.Renderer.Canvas.drawImageScaleFactor
+ * {Number} Scale factor to apply to the canvas drawImage arguments. This
+ * is always 1 except for Android 2.1 devices, to work around
+ * http://code.google.com/p/android/issues/detail?id=5141.
+ */
+OpenLayers.Renderer.Canvas.drawImageScaleFactor = null;
+/* ======================================================================
+ 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
+ * {<OpenLayers.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
+ * {<OpenLayers.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
+ * <checkModifiers>. 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 - {<OpenLayers.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 <keyMask> is set, this always
+ * returns true. If a <keyMask> 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<len; i++) {
+ if (this[events[i]]) {
+ this.register(events[i], this[events[i]]);
+ }
+ }
+ this.active = true;
+ return true;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Turn off the handler. Returns false if the handler was already inactive.
+ *
+ * Returns:
+ * {Boolean} The handler was deactivated.
+ */
+ deactivate: function() {
+ if(!this.active) {
+ return false;
+ }
+ // unregister event handlers defined on this class.
+ var events = OpenLayers.Events.prototype.BROWSER_EVENTS;
+ for (var i=0, len=events.length; i<len; i++) {
+ if (this[events[i]]) {
+ this.unregister(events[i], this[events[i]]);
+ }
+ }
+ this.touch = false;
+ this.active = false;
+ return true;
+ },
+
+ /**
+ * Method: startTouch
+ * Start touch events, this method must be called by subclasses in
+ * "touchstart" method. When touch events are started <touch> 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<len; i++) {
+ if (this[events[i]]) {
+ this.unregister(events[i], this[events[i]]);
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: callback
+ * Trigger the control's named callback with the given arguments
+ *
+ * Parameters:
+ * name - {String} The key for the callback that is one of the properties
+ * of the handler's callbacks object.
+ * args - {Array(*)} An array of arguments (any type) with which to call
+ * the callback (defined by the control).
+ */
+ callback: function (name, args) {
+ if (name && this.callbacks[name]) {
+ this.callbacks[name].apply(this.control, args);
+ }
+ },
+
+ /**
+ * Method: register
+ * register an event on the map
+ */
+ register: function (name, method) {
+ // TODO: deal with registerPriority in 3.0
+ this.map.events.registerPriority(name, this, method);
+ this.map.events.registerPriority(name, this, this.setEvent);
+ },
+
+ /**
+ * Method: unregister
+ * unregister an event from the map
+ */
+ unregister: function (name, method) {
+ this.map.events.unregister(name, this, method);
+ this.map.events.unregister(name, this, this.setEvent);
+ },
+
+ /**
+ * Method: setEvent
+ * With each registered browser event, the handler sets its own evt
+ * property. This property can be accessed by controls if needed
+ * to get more information about the event that the handler is
+ * processing.
+ *
+ * This allows modifier keys on the event to be checked (alt, shift, ctrl,
+ * and meta cannot be checked with the keyboard handler). For a
+ * control to determine which modifier keys are associated with the
+ * event that a handler is currently processing, it should access
+ * (code)handler.evt.altKey || handler.evt.shiftKey ||
+ * handler.evt.ctrlKey || handler.evt.metaKey(end).
+ *
+ * Parameters:
+ * evt - {Event} The browser event.
+ */
+ setEvent: function(evt) {
+ this.evt = evt;
+ return true;
+ },
+
+ /**
+ * Method: destroy
+ * Deconstruct the handler.
+ */
+ destroy: function () {
+ // unregister event listeners
+ this.deactivate();
+ // eliminate circular references
+ this.control = this.map = null;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler"
+});
+
+/**
+ * Constant: OpenLayers.Handler.MOD_NONE
+ * If set as the <keyMask>, <checkModifiers> returns false if any key is down.
+ */
+OpenLayers.Handler.MOD_NONE = 0;
+
+/**
+ * Constant: OpenLayers.Handler.MOD_SHIFT
+ * If set as the <keyMask>, <checkModifiers> returns false if Shift is down.
+ */
+OpenLayers.Handler.MOD_SHIFT = 1;
+
+/**
+ * Constant: OpenLayers.Handler.MOD_CTRL
+ * If set as the <keyMask>, <checkModifiers> returns false if Ctrl is down.
+ */
+OpenLayers.Handler.MOD_CTRL = 2;
+
+/**
+ * Constant: OpenLayers.Handler.MOD_ALT
+ * If set as the <keyMask>, <checkModifiers> returns false if Alt is down.
+ */
+OpenLayers.Handler.MOD_ALT = 4;
+
+/**
+ * Constant: OpenLayers.Handler.MOD_META
+ * If set as the <keyMask>, <checkModifiers> returns false if Cmd is down.
+ */
+OpenLayers.Handler.MOD_META = 8;
+
+
+/* ======================================================================
+ OpenLayers/Handler/Drag.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Drag
+ * The drag handler is used to deal with sequences of browser events related
+ * to dragging. The handler is used by controls that want to know when
+ * a drag sequence begins, when a drag is happening, and when it has
+ * finished.
+ *
+ * Controls that use the drag handler typically construct it with callbacks
+ * for 'down', 'move', and 'done'. Callbacks for these keys are called
+ * when the drag begins, with each move, and when the drag is done. In
+ * addition, controls can have callbacks keyed to 'up' and 'out' if they
+ * care to differentiate between the types of events that correspond with
+ * the end of a drag sequence. If no drag actually occurs (no mouse move)
+ * the 'down' and 'up' callbacks will be called, but not the 'done'
+ * callback.
+ *
+ * Create a new drag handler with the <OpenLayers.Handler.Drag> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+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
+ * {<OpenLayers.Pixel>} The last pixel location of the drag.
+ */
+ last: null,
+
+ /**
+ * Property: start
+ * {<OpenLayers.Pixel>} 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 - {<OpenLayers.Control>} The control that is making use of
+ * this handler. If a handler is being used without a control, the
+ * handlers setMap method must be overridden to deal properly with
+ * the map.
+ * callbacks - {Object} An object containing a single function to be
+ * called when the drag operation is finished. The callback should
+ * expect to recieve a single argument, the pixel location of the event.
+ * Callbacks for 'move' and 'done' are supported. You can also speficy
+ * callbacks for 'down', 'up', and 'out' to respond to those events.
+ * options - {Object}
+ */
+ initialize: function(control, callbacks, options) {
+ OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+
+ if (this.documentDrag === true) {
+ var me = this;
+ this._docMove = function(evt) {
+ me.mousemove({
+ xy: {x: evt.clientX, y: evt.clientY},
+ element: document
+ });
+ };
+ this._docUp = function(evt) {
+ me.mouseup({xy: {x: evt.clientX, y: evt.clientY}});
+ };
+ }
+ },
+
+
+ /**
+ * Method: dragstart
+ * This private method is factorized from mousedown and touchstart methods
+ *
+ * Parameters:
+ * evt - {Event} The event
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ dragstart: function (evt) {
+ var propagate = true;
+ this.dragging = false;
+ if (this.checkModifiers(evt) &&
+ (OpenLayers.Event.isLeftClick(evt) ||
+ OpenLayers.Event.isSingleTouch(evt))) {
+ this.started = true;
+ this.start = evt.xy;
+ this.last = evt.xy;
+ OpenLayers.Element.addClass(
+ this.map.viewPortDiv, "olDragDown"
+ );
+ this.down(evt);
+ this.callback("down", [evt.xy]);
+
+ // prevent document dragging
+ OpenLayers.Event.preventDefault(evt);
+
+ if(!this.oldOnselectstart) {
+ this.oldOnselectstart = document.onselectstart ?
+ document.onselectstart : OpenLayers.Function.True;
+ }
+ document.onselectstart = OpenLayers.Function.False;
+
+ propagate = !this.stopDown;
+ } else {
+ this.started = false;
+ this.start = null;
+ this.last = null;
+ }
+ return propagate;
+ },
+
+ /**
+ * Method: dragmove
+ * This private method is factorized from mousemove and touchmove methods
+ *
+ * Parameters:
+ * evt - {Event} The event
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ dragmove: function (evt) {
+ this.lastMoveEvt = evt;
+ if (this.started && !this.timeoutId && (evt.xy.x != this.last.x ||
+ evt.xy.y != this.last.y)) {
+ if(this.documentDrag === true && this.documentEvents) {
+ if(evt.element === document) {
+ this.adjustXY(evt);
+ // do setEvent manually because the documentEvents are not
+ // registered with the map
+ this.setEvent(evt);
+ } else {
+ this.removeDocumentEvents();
+ }
+ }
+ if (this.interval > 0) {
+ this.timeoutId = setTimeout(
+ OpenLayers.Function.bind(this.removeTimeout, this),
+ this.interval);
+ }
+ this.dragging = true;
+
+ this.move(evt);
+ this.callback("move", [evt.xy]);
+ if(!this.oldOnselectstart) {
+ this.oldOnselectstart = document.onselectstart;
+ document.onselectstart = OpenLayers.Function.False;
+ }
+ this.last = evt.xy;
+ }
+ return true;
+ },
+
+ /**
+ * Method: dragend
+ * This private method is factorized from mouseup and touchend methods
+ *
+ * Parameters:
+ * evt - {Event} The event
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ dragend: function (evt) {
+ if (this.started) {
+ if(this.documentDrag === true && this.documentEvents) {
+ this.adjustXY(evt);
+ this.removeDocumentEvents();
+ }
+ var dragged = (this.start != this.last);
+ this.started = false;
+ this.dragging = false;
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv, "olDragDown"
+ );
+ this.up(evt);
+ this.callback("up", [evt.xy]);
+ if(dragged) {
+ this.callback("done", [evt.xy]);
+ }
+ document.onselectstart = this.oldOnselectstart;
+ }
+ return true;
+ },
+
+ /**
+ * The four methods below (down, move, up, and out) are used by subclasses
+ * to do their own processing related to these mouse events.
+ */
+
+ /**
+ * Method: down
+ * This method is called during the handling of the mouse down event.
+ * Subclasses can do their own processing here.
+ *
+ * Parameters:
+ * evt - {Event} The mouse down event
+ */
+ down: function(evt) {
+ },
+
+ /**
+ * Method: move
+ * This method is called during the handling of the mouse move event.
+ * Subclasses can do their own processing here.
+ *
+ * Parameters:
+ * evt - {Event} The mouse move event
+ *
+ */
+ move: function(evt) {
+ },
+
+ /**
+ * Method: up
+ * This method is called during the handling of the mouse up event.
+ * Subclasses can do their own processing here.
+ *
+ * Parameters:
+ * evt - {Event} The mouse up event
+ */
+ up: function(evt) {
+ },
+
+ /**
+ * Method: out
+ * This method is called during the handling of the mouse out event.
+ * Subclasses can do their own processing here.
+ *
+ * Parameters:
+ * evt - {Event} The mouse out event
+ */
+ out: function(evt) {
+ },
+
+ /**
+ * The methods below are part of the magic of event handling. Because
+ * they are named like browser events, they are registered as listeners
+ * for the events they represent.
+ */
+
+ /**
+ * Method: mousedown
+ * Handle mousedown events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ mousedown: function(evt) {
+ return this.dragstart(evt);
+ },
+
+ /**
+ * Method: touchstart
+ * Handle touchstart events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchstart: function(evt) {
+ this.startTouch();
+ return this.dragstart(evt);
+ },
+
+ /**
+ * Method: mousemove
+ * Handle mousemove events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ mousemove: function(evt) {
+ return this.dragmove(evt);
+ },
+
+ /**
+ * Method: touchmove
+ * Handle touchmove events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchmove: function(evt) {
+ return this.dragmove(evt);
+ },
+
+ /**
+ * Method: removeTimeout
+ * Private. Called by mousemove() to remove the drag timeout.
+ */
+ removeTimeout: function() {
+ this.timeoutId = null;
+ // if timeout expires while we're still dragging (mouseup
+ // hasn't occurred) then call mousemove to move to the
+ // correct position
+ if(this.dragging) {
+ this.mousemove(this.lastMoveEvt);
+ }
+ },
+
+ /**
+ * Method: mouseup
+ * Handle mouseup events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ mouseup: function(evt) {
+ return this.dragend(evt);
+ },
+
+ /**
+ * Method: touchend
+ * Handle touchend events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchend: function(evt) {
+ // override evt.xy with last position since touchend does not have
+ // any touch position
+ evt.xy = this.last;
+ return this.dragend(evt);
+ },
+
+ /**
+ * Method: mouseout
+ * Handle mouseout events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ mouseout: function (evt) {
+ if (this.started && OpenLayers.Util.mouseLeft(evt, this.map.viewPortDiv)) {
+ if(this.documentDrag === true) {
+ this.addDocumentEvents();
+ } else {
+ var dragged = (this.start != this.last);
+ this.started = false;
+ this.dragging = false;
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv, "olDragDown"
+ );
+ this.out(evt);
+ this.callback("out", []);
+ if(dragged) {
+ this.callback("done", [evt.xy]);
+ }
+ if(document.onselectstart) {
+ document.onselectstart = this.oldOnselectstart;
+ }
+ }
+ }
+ return true;
+ },
+
+ /**
+ * Method: click
+ * The drag handler captures the click event. If something else registers
+ * for clicks on the same element, its listener will not be called
+ * after a drag.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ click: function (evt) {
+ // let the click event propagate only if the mouse moved
+ return (this.start == this.last);
+ },
+
+ /**
+ * Method: activate
+ * Activate the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully activated.
+ */
+ activate: function() {
+ var activated = false;
+ if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ this.dragging = false;
+ activated = true;
+ }
+ return activated;
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivate the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ this.started = false;
+ this.dragging = false;
+ this.start = null;
+ this.last = null;
+ deactivated = true;
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv, "olDragDown"
+ );
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: adjustXY
+ * Converts event coordinates that are relative to the document body to
+ * ones that are relative to the map viewport. The latter is the default in
+ * OpenLayers.
+ *
+ * Parameters:
+ * evt - {Object}
+ */
+ adjustXY: function(evt) {
+ var pos = OpenLayers.Util.pagePosition(this.map.viewPortDiv);
+ evt.xy.x -= pos[0];
+ evt.xy.y -= pos[1];
+ },
+
+ /**
+ * Method: addDocumentEvents
+ * Start observing document events when documentDrag is true and the mouse
+ * cursor leaves the map viewport while dragging.
+ */
+ addDocumentEvents: function() {
+ OpenLayers.Element.addClass(document.body, "olDragDown");
+ this.documentEvents = true;
+ OpenLayers.Event.observe(document, "mousemove", this._docMove);
+ OpenLayers.Event.observe(document, "mouseup", this._docUp);
+ },
+
+ /**
+ * Method: removeDocumentEvents
+ * Stops observing document events when documentDrag is true and the mouse
+ * cursor re-enters the map viewport while dragging.
+ */
+ removeDocumentEvents: function() {
+ OpenLayers.Element.removeClass(document.body, "olDragDown");
+ this.documentEvents = false;
+ OpenLayers.Event.stopObserving(document, "mousemove", this._docMove);
+ OpenLayers.Event.stopObserving(document, "mouseup", this._docUp);
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Drag"
+});
+/* ======================================================================
+ OpenLayers/Handler/Keyboard.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ * @requires OpenLayers/Events.js
+ */
+
+/**
+ * Class: OpenLayers.handler.Keyboard
+ * A handler for keyboard events. Create a new instance with the
+ * <OpenLayers.Handler.Keyboard> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+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 - {<OpenLayers.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<len; i++) {
+ OpenLayers.Event.observe(
+ this.observeElement, this.KEY_EVENTS[i], this.eventListener);
+ }
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: deactivate
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ for (var i=0, len=this.KEY_EVENTS.length; i<len; i++) {
+ OpenLayers.Event.stopObserving(
+ this.observeElement, this.KEY_EVENTS[i], this.eventListener);
+ }
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: handleKeyEvent
+ */
+ handleKeyEvent: function (evt) {
+ if (this.checkModifiers(evt)) {
+ this.callback(evt.type, [evt]);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Keyboard"
+});
+/* ======================================================================
+ OpenLayers/Control/ModifyFeature.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/Handler/Drag.js
+ * @requires OpenLayers/Handler/Keyboard.js
+ */
+
+/**
+ * Class: OpenLayers.Control.ModifyFeature
+ * Control to modify features. When activated, a click renders the vertices
+ * of a feature - these vertices can then be dragged. By default, the
+ * delete key will delete the vertex under the mouse. New features are
+ * added by dragging "virtual vertices" between vertices. Create a new
+ * control with the <OpenLayers.Control.ModifyFeature> constructor.
+ *
+ * Inherits From:
+ * - <OpenLayers.Control>
+ */
+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 <selectFeature> method with the target feature.
+ * Note that you must call the <unselectFeature> method to finish
+ * feature modification in standalone mode (before starting to modify
+ * another feature).
+ */
+ standalone: false,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>}
+ */
+ layer: null,
+
+ /**
+ * Property: feature
+ * {<OpenLayers.Feature.Vector>} Feature currently available for modification.
+ */
+ feature: null,
+
+ /**
+ * Property: vertex
+ * {<OpenLayers.Feature.Vector>} Vertex currently being modified.
+ */
+ vertex: null,
+
+ /**
+ * Property: vertices
+ * {Array(<OpenLayers.Feature.Vector>)} Verticies currently available
+ * for dragging.
+ */
+ vertices: null,
+
+ /**
+ * Property: virtualVertices
+ * {Array(<OpenLayers.Feature.Vector>)} 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 <virtualStyle> 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
+ * {<OpenLayers.Feature.Vector>} A handle for rotating/resizing a feature.
+ */
+ radiusHandle: null,
+
+ /**
+ * Property: dragHandle
+ * {<OpenLayers.Feature.Vector>} 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 - {<OpenLayers.Layer.Vector>} 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 - {<OpenLayers.Feature.Vector>} 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 - {<OpenLayers.Feature.Vector>} 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 - {<OpenLayers.Feature.Vector>} 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 - {<OpenLayers.Feature.Vector>} 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 - {<OpenLayers.Feature.Vector>} The vertex being dragged.
+ * pixel - {<OpenLayers.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 - {<OpenLayers.Feature.Vector>} 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 <deleteCode> 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<numVert; ++i) {
+ component = geometry.components[i];
+ if(component.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ vertex = new OpenLayers.Feature.Vector(component);
+ vertex._sketch = true;
+ vertex.renderIntent = control.vertexRenderIntent;
+ control.vertices.push(vertex);
+ } else {
+ collectComponentVertices(component);
+ }
+ }
+
+ // add virtual vertices in the middle of each edge
+ if (control.createVertices && geometry.CLASS_NAME != "OpenLayers.Geometry.MultiPoint") {
+ for(i=0, len=geometry.components.length; i<len-1; ++i) {
+ var prevVertex = geometry.components[i];
+ var nextVertex = geometry.components[i + 1];
+ if(prevVertex.CLASS_NAME == "OpenLayers.Geometry.Point" &&
+ nextVertex.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ var x = (prevVertex.x + nextVertex.x) / 2;
+ var y = (prevVertex.y + nextVertex.y) / 2;
+ var point = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(x, y),
+ null, control.virtualStyle
+ );
+ // set the virtual parent and intended index
+ point.geometry.parent = geometry;
+ point._index = i + 1;
+ point._sketch = true;
+ control.virtualVertices.push(point);
+ }
+ }
+ }
+ }
+ }
+ collectComponentVertices.call(this, this.feature.geometry);
+ this.layer.addFeatures(this.virtualVertices, {silent: true});
+ this.layer.addFeatures(this.vertices, {silent: true});
+ },
+
+ /**
+ * Method: collectDragHandle
+ * Collect the drag handle for the selected geometry.
+ */
+ collectDragHandle: function() {
+ var geometry = this.feature.geometry;
+ var center = geometry.getBounds().getCenterLonLat();
+ var originGeometry = new OpenLayers.Geometry.Point(
+ center.lon, center.lat
+ );
+ var origin = new OpenLayers.Feature.Vector(originGeometry);
+ originGeometry.move = function(x, y) {
+ OpenLayers.Geometry.Point.prototype.move.call(this, x, y);
+ geometry.move(x, y);
+ };
+ origin._sketch = true;
+ this.dragHandle = origin;
+ this.dragHandle.renderIntent = this.vertexRenderIntent;
+ this.layer.addFeatures([this.dragHandle], {silent: true});
+ },
+
+ /**
+ * Method: collectRadiusHandle
+ * Collect the radius handle for the selected geometry.
+ */
+ collectRadiusHandle: function() {
+ var geometry = this.feature.geometry;
+ var bounds = geometry.getBounds();
+ var center = bounds.getCenterLonLat();
+ var originGeometry = new OpenLayers.Geometry.Point(
+ center.lon, center.lat
+ );
+ var radiusGeometry = new OpenLayers.Geometry.Point(
+ bounds.right, bounds.bottom
+ );
+ var radius = new OpenLayers.Feature.Vector(radiusGeometry);
+ var resize = (this.mode & OpenLayers.Control.ModifyFeature.RESIZE);
+ var reshape = (this.mode & OpenLayers.Control.ModifyFeature.RESHAPE);
+ var rotate = (this.mode & OpenLayers.Control.ModifyFeature.ROTATE);
+
+ radiusGeometry.move = function(x, y) {
+ OpenLayers.Geometry.Point.prototype.move.call(this, x, y);
+ var dx1 = this.x - originGeometry.x;
+ var dy1 = this.y - originGeometry.y;
+ var dx0 = dx1 - x;
+ var dy0 = dy1 - y;
+ if(rotate) {
+ var a0 = Math.atan2(dy0, dx0);
+ var a1 = Math.atan2(dy1, dx1);
+ var angle = a1 - a0;
+ angle *= 180 / Math.PI;
+ geometry.rotate(angle, originGeometry);
+ }
+ if(resize) {
+ var scale, ratio;
+ // 'resize' together with 'reshape' implies that the aspect
+ // ratio of the geometry will not be preserved whilst resizing
+ if (reshape) {
+ scale = dy1 / dy0;
+ ratio = (dx1 / dx0) / scale;
+ } else {
+ var l0 = Math.sqrt((dx0 * dx0) + (dy0 * dy0));
+ var l1 = Math.sqrt((dx1 * dx1) + (dy1 * dy1));
+ scale = l1 / l0;
+ }
+ geometry.resize(scale, originGeometry, ratio);
+ }
+ };
+ radius._sketch = true;
+ this.radiusHandle = radius;
+ this.radiusHandle.renderIntent = this.vertexRenderIntent;
+ this.layer.addFeatures([this.radiusHandle], {silent: true});
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the control and all handlers.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>} 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 <OpenLayers.Control.Attribution> control and the
+ * attribution placed on or near the map.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.XYZ>
+ */
+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: '<span class="olBingAttribution ${type}">' +
+ '<div><a target="_blank" href="http://www.bing.com/maps/">' +
+ '<img src="${logo}" /></a></div>${copyrights}' +
+ '<a style="white-space: nowrap" target="_blank" '+
+ 'href="http://www.microsoft.com/maps/product/terms.html">' +
+ 'Terms of Use</a></span>',
+
+ /**
+ * 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 <OpenLayers.Tile> 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<res.imageUrlSubdomains.length; ++i) {
+ this.url.push(url.replace("{subdomain}", res.imageUrlSubdomains[i]));
+ }
+ this.addOptions({
+ maxResolution: Math.min(
+ this.serverResolutions[res.zoomMin],
+ this.maxResolution || Number.POSITIVE_INFINITY
+ ),
+ numZoomLevels: Math.min(
+ res.zoomMax + 1 - res.zoomMin, this.numZoomLevels
+ )
+ }, true);
+ if (!this.isBaseLayer) {
+ this.redraw();
+ }
+ this.updateAttribution();
+ },
+
+ /**
+ * Method: getURL
+ *
+ * Paramters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ 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<ii; ++i) {
+ provider = providers[i];
+ for (j=0,jj=provider.coverageAreas.length; j<jj; ++j) {
+ coverage = provider.coverageAreas[j];
+ // axis order provided is Y,X
+ bbox = OpenLayers.Bounds.fromArray(coverage.bbox, true);
+ if (extent.intersectsBounds(bbox) &&
+ zoom <= coverage.zoomMax && zoom >= 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:
+ * {<OpenLayers.Layer.Bing>} An exact clone of this <OpenLayers.Layer.Bing>
+ */
+ clone: function(obj) {
+ if (obj == null) {
+ obj = new OpenLayers.Layer.Bing(this.options);
+ }
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]);
+ // copy/set any non-init, non-simple values here
+ return obj;
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ this.map &&
+ this.map.events.unregister("moveend", this, this.updateAttribution);
+ OpenLayers.Layer.XYZ.prototype.destroy.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Bing"
+});
+
+/**
+ * Function: OpenLayers.Layer.Bing.processMetadata
+ * This function will be bound to an instance, linked to the global scope with
+ * an id, and called by the JSONP script returned by the API.
+ *
+ * Parameters:
+ * metadata - {Object} metadata as returned by the API
+ */
+OpenLayers.Layer.Bing.processMetadata = function(metadata) {
+ this.metadata = metadata;
+ this.initLayer();
+ var script = document.getElementById(this._callbackId);
+ script.parentNode.removeChild(script);
+ window[this._callbackId] = undefined; // cannot delete from window in IE
+ delete this._callbackId;
+};
+/* ======================================================================
+ OpenLayers/Geometry/MultiLineString.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ * @requires OpenLayers/Geometry/LineString.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.MultiLineString
+ * A MultiLineString is a geometry with multiple <OpenLayers.Geometry.LineString>
+ * components.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Collection>
+ * - <OpenLayers.Geometry>
+ */
+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(<OpenLayers.Geometry.LineString>)}
+ *
+ */
+
+ /**
+ * Method: split
+ * Use this geometry (the source) to attempt to split a target geometry.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.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<len; ++i) {
+ sourceLine = this.components[i];
+ sourceSplit = false;
+ for(var j=0; j < targetParts.length; ++j) {
+ splits = sourceLine.split(targetParts[j], options);
+ if(splits) {
+ if(mutual) {
+ sourceLines = splits[0];
+ for(var k=0, klen=sourceLines.length; k<klen; ++k) {
+ if(k===0 && sourceParts.length) {
+ sourceParts[sourceParts.length-1].addComponent(
+ sourceLines[k]
+ );
+ } else {
+ sourceParts.push(
+ new OpenLayers.Geometry.MultiLineString([
+ sourceLines[k]
+ ])
+ );
+ }
+ }
+ sourceSplit = true;
+ splits = splits[1];
+ }
+ if(splits.length) {
+ // splice in new target parts
+ splits.unshift(j, 1);
+ Array.prototype.splice.apply(targetParts, splits);
+ break;
+ }
+ }
+ }
+ if(!sourceSplit) {
+ // source line was not hit
+ if(sourceParts.length) {
+ // add line to existing multi
+ sourceParts[sourceParts.length-1].addComponent(
+ sourceLine.clone()
+ );
+ } else {
+ // create a fresh multi
+ sourceParts = [
+ new OpenLayers.Geometry.MultiLineString(
+ sourceLine.clone()
+ )
+ ];
+ }
+ }
+ }
+ if(sourceParts && sourceParts.length > 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 - {<OpenLayers.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<len; ++i) {
+ targetSplit = false;
+ targetLine = this.components[i];
+ for(var j=0; j<sourceParts.length; ++j) {
+ splits = sourceParts[j].split(targetLine, options);
+ if(splits) {
+ if(mutual) {
+ sourceLines = splits[0];
+ if(sourceLines.length) {
+ // splice in new source parts
+ sourceLines.unshift(j, 1);
+ Array.prototype.splice.apply(sourceParts, sourceLines);
+ j += sourceLines.length - 2;
+ }
+ splits = splits[1];
+ if(splits.length === 0) {
+ splits = [targetLine.clone()];
+ }
+ }
+ for(var k=0, klen=splits.length; k<klen; ++k) {
+ if(k===0 && targetParts.length) {
+ targetParts[targetParts.length-1].addComponent(
+ splits[k]
+ );
+ } else {
+ targetParts.push(
+ new OpenLayers.Geometry.MultiLineString([
+ splits[k]
+ ])
+ );
+ }
+ }
+ targetSplit = true;
+ }
+ }
+ if(!targetSplit) {
+ // target component was not hit
+ if(targetParts.length) {
+ // add it to any existing multi-line
+ targetParts[targetParts.length-1].addComponent(
+ targetLine.clone()
+ );
+ } else {
+ // or start with a fresh multi-line
+ targetParts = [
+ new OpenLayers.Geometry.MultiLineString([
+ targetLine.clone()
+ ])
+ ];
+ }
+
+ }
+ }
+ } else {
+ results = geometry.split(this);
+ }
+ if(sourceParts && sourceParts.length > 1) {
+ sourceSplit = true;
+ } else {
+ sourceParts = [];
+ }
+ if(targetParts && targetParts.length > 1) {
+ targetSplit = true;
+ } else {
+ targetParts = [];
+ }
+ if(sourceSplit || targetSplit) {
+ if(mutual) {
+ results = [sourceParts, targetParts];
+ } else {
+ results = targetParts;
+ }
+ }
+ return results;
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.MultiLineString"
+});
+/* ======================================================================
+ OpenLayers/Format.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Format
+ * Base class for format reading/writing a variety of formats. Subclasses
+ * of OpenLayers.Format are expected to have read and write methods.
+ */
+OpenLayers.Format = OpenLayers.Class({
+
+ /**
+ * Property: options
+ * {Object} A reference to options passed to the constructor.
+ */
+ options: null,
+
+ /**
+ * APIProperty: externalProjection
+ * {<OpenLayers.Projection>} 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
+ * {<OpenLayers.Projection.addTransform>} for more information on
+ * custom transformations.
+ */
+ externalProjection: null,
+
+ /**
+ * APIProperty: internalProjection
+ * {<OpenLayers.Projection>} 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
+ * {<OpenLayers.Projection.addTransform>} for more information on
+ * custom transformations.
+ */
+ internalProjection: null,
+
+ /**
+ * APIProperty: data
+ * {Object} When <keepData> is true, this is the parsed string sent to
+ * <read>.
+ */
+ data: null,
+
+ /**
+ * APIProperty: keepData
+ * {Object} Maintain a reference (<data>) 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 <read>, the data property will be
+ * set to the parsed object (e.g. the json or xml object).
+ *
+ * Returns:
+ * An instance of OpenLayers.Format
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ this.options = options;
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up.
+ */
+ destroy: function() {
+ },
+
+ /**
+ * Method: read
+ * Read data from a string, and return an object whose type depends on the
+ * subclass.
+ *
+ * Parameters:
+ * data - {string} Data to read/parse.
+ *
+ * Returns:
+ * Depends on the subclass
+ */
+ read: function(data) {
+ throw new Error('Read not implemented.');
+ },
+
+ /**
+ * Method: write
+ * Accept an object, and return a string.
+ *
+ * Parameters:
+ * object - {Object} Object to be serialized
+ *
+ * Returns:
+ * {String} A string representation of the object.
+ */
+ write: function(object) {
+ throw new Error('Write not implemented.');
+ },
+
+ CLASS_NAME: "OpenLayers.Format"
+});
+/* ======================================================================
+ OpenLayers/Format/XML.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format.js
+ */
+
+/**
+ * Class: OpenLayers.Format.XML
+ * Read and write XML. For cross-browser XML generation, use methods on an
+ * instance of the XML format class instead of on <code>document<end>.
+ * The DOM creation and traversing methods exposed here all mimic the
+ * W3C XML DOM methods. Create a new parser with the
+ * <OpenLayers.Format.XML> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format>
+ */
+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
+ * <setNamespace> 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 <setNamespace> 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 <readers> 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 <code>document<end>
+ * 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<len; ++i) {
+ potentialNode = allNodes[i];
+ fullName = (potentialNode.prefix) ?
+ (potentialNode.prefix + ":" + name) : name;
+ if((name == "*") || (fullName == potentialNode.nodeName)) {
+ if((uri == "*") || (uri == potentialNode.namespaceURI)) {
+ elements.push(potentialNode);
+ }
+ }
+ }
+ }
+ return elements;
+ },
+
+ /**
+ * APIMethod: getAttributeNodeNS
+ * Get an attribute node given the namespace URI and local name.
+ *
+ * Parameters:
+ * node - {Element} Node on which to search for attribute nodes.
+ * uri - {String} Namespace URI.
+ * name - {String} Local name of the attribute (without the prefix).
+ *
+ * Returns:
+ * {DOMElement} An attribute node or null if none found.
+ */
+ getAttributeNodeNS: function(node, uri, name) {
+ var attributeNode = null;
+ if(node.getAttributeNodeNS) {
+ attributeNode = node.getAttributeNodeNS(uri, name);
+ } else {
+ var attributes = node.attributes;
+ var potentialNode, fullName;
+ for(var i=0, len=attributes.length; i<len; ++i) {
+ potentialNode = attributes[i];
+ if(potentialNode.namespaceURI == uri) {
+ fullName = (potentialNode.prefix) ?
+ (potentialNode.prefix + ":" + name) : name;
+ if(fullName == potentialNode.nodeName) {
+ attributeNode = potentialNode;
+ break;
+ }
+ }
+ }
+ }
+ return attributeNode;
+ },
+
+ /**
+ * APIMethod: getAttributeNS
+ * Get an attribute value given the namespace URI and local name.
+ *
+ * Parameters:
+ * node - {Element} Node on which to search for an attribute.
+ * uri - {String} Namespace URI.
+ * name - {String} Local name of the attribute (without the prefix).
+ *
+ * Returns:
+ * {String} An attribute value or and empty string if none found.
+ */
+ getAttributeNS: function(node, uri, name) {
+ var attributeValue = "";
+ if(node.getAttributeNS) {
+ attributeValue = node.getAttributeNS(uri, name) || "";
+ } else {
+ var attributeNode = this.getAttributeNodeNS(node, uri, name);
+ if(attributeNode) {
+ attributeValue = attributeNode.nodeValue;
+ }
+ }
+ return attributeValue;
+ },
+
+ /**
+ * APIMethod: getChildValue
+ * Get the textual value of the node if it exists, or return an
+ * optional default string. Returns an empty string if no first child
+ * exists and no default value is supplied.
+ *
+ * Parameters:
+ * node - {DOMElement} The element used to look for a first child value.
+ * def - {String} Optional string to return in the event that no
+ * first child value exists.
+ *
+ * Returns:
+ * {String} The value of the first child of the given node.
+ */
+ getChildValue: function(node, def) {
+ var value = def || "";
+ if(node) {
+ for(var child=node.firstChild; child; child=child.nextSibling) {
+ switch(child.nodeType) {
+ case 3: // text node
+ case 4: // cdata section
+ value += child.nodeValue;
+ }
+ }
+ }
+ return value;
+ },
+
+ /**
+ * APIMethod: isSimpleContent
+ * Test if the given node has only simple content (i.e. no child element
+ * nodes).
+ *
+ * Parameters:
+ * node - {DOMElement} An element node.
+ *
+ * Returns:
+ * {Boolean} The node has no child element nodes (nodes of type 1).
+ */
+ isSimpleContent: function(node) {
+ var simple = true;
+ for(var child=node.firstChild; child; child=child.nextSibling) {
+ if(child.nodeType === 1) {
+ simple = false;
+ break;
+ }
+ }
+ return simple;
+ },
+
+ /**
+ * APIMethod: contentType
+ * Determine the content type for a given node.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ *
+ * Returns:
+ * {Integer} One of OpenLayers.Format.XML.CONTENT_TYPE.{EMPTY,SIMPLE,COMPLEX,MIXED}
+ * if the node has no, simple, complex, or mixed content.
+ */
+ contentType: function(node) {
+ var simple = false,
+ complex = false;
+
+ var type = OpenLayers.Format.XML.CONTENT_TYPE.EMPTY;
+
+ for(var child=node.firstChild; child; child=child.nextSibling) {
+ switch(child.nodeType) {
+ case 1: // element
+ complex = true;
+ break;
+ case 8: // comment
+ break;
+ default:
+ simple = true;
+ }
+ if(complex && simple) {
+ break;
+ }
+ }
+
+ if(complex && simple) {
+ type = OpenLayers.Format.XML.CONTENT_TYPE.MIXED;
+ } else if(complex) {
+ return OpenLayers.Format.XML.CONTENT_TYPE.COMPLEX;
+ } else if(simple) {
+ return OpenLayers.Format.XML.CONTENT_TYPE.SIMPLE;
+ }
+ return type;
+ },
+
+ /**
+ * APIMethod: hasAttributeNS
+ * Determine whether a node has a particular attribute matching the given
+ * name and namespace.
+ *
+ * Parameters:
+ * node - {Element} Node on which to search for an attribute.
+ * uri - {String} Namespace URI.
+ * name - {String} Local name of the attribute (without the prefix).
+ *
+ * Returns:
+ * {Boolean} The node has an attribute matching the name and namespace.
+ */
+ hasAttributeNS: function(node, uri, name) {
+ var found = false;
+ if(node.hasAttributeNS) {
+ found = node.hasAttributeNS(uri, name);
+ } else {
+ found = !!this.getAttributeNodeNS(node, uri, name);
+ }
+ return found;
+ },
+
+ /**
+ * APIMethod: setAttributeNS
+ * Adds a new attribute or changes the value of an attribute with the given
+ * namespace and name.
+ *
+ * Parameters:
+ * node - {Element} Element node on which to set the attribute.
+ * uri - {String} Namespace URI for the attribute.
+ * name - {String} Qualified name (prefix:localname) for the attribute.
+ * value - {String} Attribute value.
+ */
+ setAttributeNS: function(node, uri, name, value) {
+ if(node.setAttributeNS) {
+ node.setAttributeNS(uri, name, value);
+ } else {
+ if(this.xmldom) {
+ if(uri) {
+ var attribute = node.ownerDocument.createNode(
+ 2, name, uri
+ );
+ attribute.nodeValue = value;
+ node.setAttributeNode(attribute);
+ } else {
+ node.setAttribute(name, value);
+ }
+ } else {
+ throw "setAttributeNS not implemented";
+ }
+ }
+ },
+
+ /**
+ * Method: createElementNSPlus
+ * Shorthand for creating namespaced elements with optional attributes and
+ * child text nodes.
+ *
+ * Parameters:
+ * name - {String} The qualified node name.
+ * options - {Object} Optional object for node configuration.
+ *
+ * Valid options:
+ * uri - {String} Optional namespace uri for the element - supply a prefix
+ * instead if the namespace uri is a property of the format's namespace
+ * object.
+ * attributes - {Object} Optional attributes to be set using the
+ * <setAttributes> 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), <readSelf> 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<len; ++i) {
+ child = children[i];
+ if(child.nodeType == 1) {
+ this.readNode(child, obj);
+ }
+ }
+ return obj;
+ },
+
+ /**
+ * Method: writeNode
+ * Shorthand for applying one of the named writers and appending the
+ * results to a node. If a qualified name is not provided for the
+ * second argument (and a local name is used instead), the namespace
+ * of the parent node will be assumed.
+ *
+ * Parameters:
+ * name - {String} The name of a node to generate. If a qualified name
+ * (e.g. "pre:Name") is used, the namespace prefix is assumed to be
+ * in the <writers> 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<len; ++i) {
+ attr = node.attributes[i];
+ if(attr.prefix === "xmlns" && attr.name === "xmlns:" + prefix) {
+ uri = attr.value || null;
+ break outer;
+ } else if(attr.name === "xmlns" && prefix === null) {
+ uri = attr.value || null;
+ break outer;
+ }
+ }
+ }
+ uri = this.lookupNamespaceURI(node.parentNode, prefix);
+ break outer;
+ case 2: // ATTRIBUTE_NODE
+ uri = this.lookupNamespaceURI(node.ownerElement, prefix);
+ break outer;
+ case 9: // DOCUMENT_NODE
+ uri = this.lookupNamespaceURI(node.documentElement, prefix);
+ break outer;
+ case 6: // ENTITY_NODE
+ case 12: // NOTATION_NODE
+ case 10: // DOCUMENT_TYPE_NODE
+ case 11: // DOCUMENT_FRAGMENT_NODE
+ break outer;
+ default:
+ // TEXT_NODE (3), CDATA_SECTION_NODE (4), ENTITY_REFERENCE_NODE (5),
+ // PROCESSING_INSTRUCTION_NODE (7), COMMENT_NODE (8)
+ uri = this.lookupNamespaceURI(node.parentNode, prefix);
+ break outer;
+ }
+ }
+ }
+ return uri;
+ },
+
+ /**
+ * Method: getXMLDoc
+ * Get an XML document for nodes that are not supported in HTML (e.g.
+ * createCDATASection). On IE, this will either return an existing or
+ * create a new <xmldom> on the instance. On other browsers, this will
+ * either return an existing or create a new shared document (see
+ * <OpenLayers.Format.XML.document>).
+ *
+ * Returns:
+ * {XMLDocument}
+ */
+ getXMLDoc: function() {
+ if (!OpenLayers.Format.XML.document && !this.xmldom) {
+ if (document.implementation && document.implementation.createDocument) {
+ OpenLayers.Format.XML.document =
+ document.implementation.createDocument("", "", null);
+ } else if (!this.xmldom && window.ActiveXObject) {
+ this.xmldom = new ActiveXObject("Microsoft.XMLDOM");
+ }
+ }
+ return OpenLayers.Format.XML.document || this.xmldom;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.XML"
+
+});
+
+OpenLayers.Format.XML.CONTENT_TYPE = {EMPTY: 0, SIMPLE: 1, COMPLEX: 2, MIXED: 3};
+
+/**
+ * APIFunction: OpenLayers.Format.XML.lookupNamespaceURI
+ * Takes a prefix and returns the namespace URI associated with it on the given
+ * node if found (and null if not). Supplying null for the prefix will
+ * return the default namespace.
+ *
+ * For browsers that support it, this calls the native lookupNamesapceURI
+ * function. In other browsers, this is an implementation of
+ * http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespaceURI.
+ *
+ * For browsers that don't support the attribute.ownerElement property, this
+ * method cannot be called on attribute nodes.
+ *
+ * Parameters:
+ * node - {DOMElement} The node from which to start looking.
+ * prefix - {String} The prefix to lookup or null to lookup the default namespace.
+ *
+ * Returns:
+ * {String} The namespace URI for the given prefix. Returns null if the prefix
+ * cannot be found or the node is the wrong type.
+ */
+OpenLayers.Format.XML.lookupNamespaceURI = OpenLayers.Function.bind(
+ OpenLayers.Format.XML.prototype.lookupNamespaceURI,
+ OpenLayers.Format.XML.prototype
+);
+
+/**
+ * Property: OpenLayers.Format.XML.document
+ * {XMLDocument} XML document to reuse for creating non-HTML compliant nodes,
+ * like document.createCDATASection.
+ */
+OpenLayers.Format.XML.document = null;
+/* ======================================================================
+ OpenLayers/Format/OGCExceptionReport.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ */
+
+/**
+ * Class: OpenLayers.Format.OGCExceptionReport
+ * Class to read exception reports for various OGC services and versions.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+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>
+ */
+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:
+ * {<OpenLayers.Format>}
+ */
+ getParser: function(version) {
+ version = version || this.defaultVersion;
+ var profile = this.profile ? "_" + this.profile : "";
+ if(!this.parser || this.parser.VERSION != version) {
+ var format = OpenLayers.Format[this.name][
+ "v" + version.replace(/\./g, "_") + profile
+ ];
+ if(!format) {
+ if (profile !== "" && this.allowFallback) {
+ // fallback to the non-profiled version of the parser
+ profile = "";
+ format = OpenLayers.Format[this.name][
+ "v" + version.replace(/\./g, "_")
+ ];
+ }
+ if (!format) {
+ throw "Can't find a " + this.name + " parser for version " +
+ version + profile;
+ }
+ }
+ this.parser = new format(this.options);
+ }
+ return this.parser;
+ },
+
+ /**
+ * APIMethod: write
+ * Write a document.
+ *
+ * Parameters:
+ * obj - {Object} An object representing the document.
+ * options - {Object} Optional configuration object.
+ *
+ * Returns:
+ * {String} The document as a string
+ */
+ write: function(obj, options) {
+ var version = this.getVersion(null, options);
+ this.parser = this.getParser(version);
+ var root = this.parser.write(obj, options);
+ if (this.stringifyOutput === false) {
+ return root;
+ } else {
+ return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+ }
+ },
+
+ /**
+ * APIMethod: read
+ * Read a doc and return an object representing the document.
+ *
+ * Parameters:
+ * data - {String | DOMElement} Data to read.
+ * options - {Object} Options for the reader.
+ *
+ * Returns:
+ * {Object} An object representing the document.
+ */
+ read: function(data, options) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ var root = data.documentElement;
+ var version = this.getVersion(root);
+ this.parser = this.getParser(version); // Select the parser
+ var obj = this.parser.read(data, options); // Parse the data
+
+ var errorProperty = this.parser.errorProperty || null;
+ if (errorProperty !== null && obj[errorProperty] === undefined) {
+ // an error must have happened, so parse it and report back
+ var format = new OpenLayers.Format.OGCExceptionReport();
+ obj.error = format.read(data);
+ }
+ obj.version = version;
+ return obj;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.XML.VersionedOGC"
+});
+/* ======================================================================
+ OpenLayers/Feature.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Feature
+ * Features are combinations of geography and attributes. The OpenLayers.Feature
+ * class specifically combines a marker and a lonlat.
+ */
+OpenLayers.Feature = OpenLayers.Class({
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer>}
+ */
+ layer: null,
+
+ /**
+ * Property: id
+ * {String}
+ */
+ id: null,
+
+ /**
+ * Property: lonlat
+ * {<OpenLayers.LonLat>}
+ */
+ lonlat: null,
+
+ /**
+ * Property: data
+ * {Object}
+ */
+ data: null,
+
+ /**
+ * Property: marker
+ * {<OpenLayers.Marker>}
+ */
+ marker: null,
+
+ /**
+ * APIProperty: popupClass
+ * {<OpenLayers.Class>} The class which will be used to instantiate
+ * a new Popup. Default is <OpenLayers.Popup.Anchored>.
+ */
+ popupClass: null,
+
+ /**
+ * Property: popup
+ * {<OpenLayers.Popup>}
+ */
+ popup: null,
+
+ /**
+ * Constructor: OpenLayers.Feature
+ * Constructor for features.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ * lonlat - {<OpenLayers.LonLat>}
+ * data - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Feature>}
+ */
+ 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:
+ * {<OpenLayers.Marker>} 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:
+ * {<OpenLayers.Popup>} 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 <OpenLayers.Popup>.
+ *
+ */
+ 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
+ * <OpenLayers.Feature.Vector.style> objects.
+ *
+ * Inherits from:
+ * - <OpenLayers.Feature>
+ */
+OpenLayers.Feature.Vector = OpenLayers.Class(OpenLayers.Feature, {
+
+ /**
+ * Property: fid
+ * {String}
+ */
+ fid: null,
+
+ /**
+ * APIProperty: geometry
+ * {<OpenLayers.Geometry>}
+ */
+ geometry: null,
+
+ /**
+ * APIProperty: attributes
+ * {Object} This object holds arbitrary, serializable properties that
+ * describe the feature.
+ */
+ attributes: null,
+
+ /**
+ * Property: bounds
+ * {<OpenLayers.Bounds>} The box bounding that feature's geometry, that
+ * property can be set by an <OpenLayers.Format> 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
+ * {<OpenLayers.HTTP>} 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 <OpenLayers.Format.WFST.v1>, and written by
+ * <OpenLayers.Control.ModifyFeature>, 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 <OpenLayers.Format.WFST.v1> 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 - {<OpenLayers.Geometry>} The geometry that this feature
+ * represents.
+ * attributes - {Object} An optional object that will be mapped to the
+ * <attributes> 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:
+ * {<OpenLayers.Feature.Vector>} 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:
+ * {<OpenLayers.Marker>} 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:
+ * {<OpenLayers.Popup>} For now just returns null
+ */
+ createPopup: function() {
+ return null;
+ },
+
+ /**
+ * Method: atPoint
+ * Determins whether the feature intersects with the specified location.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.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 - {<OpenLayers.LonLat> or <OpenLayers.Pixel>} 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
+ * {<String>} 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(<OpenLayers.Rule>)}
+ */
+ 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 <defaultsPerSymbolizer> 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 <defaultStyle> will extend the symbolizer
+ * of every rule. Properties of the <defaultStyle> 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(<OpenLayers.Rule>)} List of rules to be added to the
+ * style.
+ *
+ * Returns:
+ * {<OpenLayers.Style>}
+ */
+ 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<len; i++) {
+ this.rules[i].destroy();
+ this.rules[i] = null;
+ }
+ this.rules = null;
+ this.defaultStyle = null;
+ },
+
+ /**
+ * Method: createSymbolizer
+ * creates a style by applying all feature-dependent rules to the base
+ * style.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature>} 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<len; i++) {
+ rule = rules[i];
+ // does the rule apply?
+ var applies = rule.evaluate(feature);
+
+ if(applies) {
+ if(rule instanceof OpenLayers.Rule && rule.elseFilter) {
+ elseRules.push(rule);
+ } else {
+ appliedRules = true;
+ this.applySymbolizer(rule, style, feature);
+ }
+ }
+ }
+
+ // if no other rules apply, apply the rules with else filters
+ if(appliedRules == false && elseRules.length > 0) {
+ appliedRules = true;
+ for(var i=0, len=elseRules.length; i<len; i++) {
+ this.applySymbolizer(elseRules[i], style, feature);
+ }
+ }
+
+ // don't display if there were rules but none applied
+ if(rules.length > 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 - {<OpenLayers.Rule>}
+ * style - {Object}
+ * feature - {<OpenLayer.Feature.Vector>}
+ *
+ * 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
+ * <this.propertyStyles>.
+ *
+ * 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<len; i++) {
+ symbolizer = rules[i].symbolizer;
+ for (var key in symbolizer) {
+ value = symbolizer[key];
+ if (typeof value == "object") {
+ // symbolizer key is "Point", "Line" or "Polygon"
+ this.addPropertyStyles(propertyStyles, value);
+ } else {
+ // symbolizer is a hash of style properties
+ this.addPropertyStyles(propertyStyles, symbolizer);
+ break;
+ }
+ }
+ }
+ return propertyStyles;
+ },
+
+ /**
+ * Method: addPropertyStyles
+ *
+ * Parameters:
+ * propertyStyles - {Object} hash to add new property styles to. Will be
+ * modified inline
+ * symbolizer - {Object} search this symbolizer for property styles
+ *
+ * Returns:
+ * {Object} propertyStyles hash
+ */
+ addPropertyStyles: function(propertyStyles, symbolizer) {
+ var property;
+ for (var key in symbolizer) {
+ property = symbolizer[key];
+ if (typeof property == "string" &&
+ property.match(/\$\{\w+\}/)) {
+ propertyStyles[key] = true;
+ }
+ }
+ return propertyStyles;
+ },
+
+ /**
+ * APIMethod: addRules
+ * Adds rules to this style.
+ *
+ * Parameters:
+ * rules - {Array(<OpenLayers.Rule>)}
+ */
+ 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 - {<OpenLayers.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<len; i++) {
+ if (geometry.CLASS_NAME.indexOf(prefixes[i]) != -1) {
+ return prefixes[i];
+ }
+ }
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this style.
+ *
+ * Returns:
+ * {<OpenLayers.Style>} 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<len; ++i) {
+ options.rules.push(this.rules[i].clone());
+ }
+ }
+ // clone context
+ options.context = this.context && OpenLayers.Util.extend({}, this.context);
+ //clone default style
+ var defaultStyle = OpenLayers.Util.extend({}, this.defaultStyle);
+ return new OpenLayers.Style(defaultStyle, options);
+ },
+
+ CLASS_NAME: "OpenLayers.Style"
+});
+
+
+/**
+ * Function: createLiteral
+ * converts a style value holding a combination of PropertyName and Literal
+ * into a Literal, taking the property values from the passed features.
+ *
+ * Parameters:
+ * value - {String} value to parse. If this string contains a construct like
+ * "foo ${bar}", then "foo " will be taken as literal, and "${bar}"
+ * will be replaced by the value of the "bar" attribute of the passed
+ * feature.
+ * context - {Object} context to take attribute values from
+ * feature - {<OpenLayers.Feature.Vector>} optional feature to pass to
+ * <OpenLayers.String.format> 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:
+ * {<OpenLayers.Filter>}
+ */
+ 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:
+ * {<OpenLayers.Filter>} Clone of this filter.
+ */
+ clone: function() {
+ return null;
+ },
+
+ /**
+ * APIMethod: toString
+ *
+ * Returns:
+ * {String} Include <OpenLayers.Format.CQL> in your build to get a CQL
+ * representation of the filter returned. Otherwise "[Object object]"
+ * will be returned.
+ */
+ toString: function() {
+ var string;
+ if (OpenLayers.Format && OpenLayers.Format.CQL) {
+ string = OpenLayers.Format.CQL.prototype.write(this);
+ } else {
+ string = Object.prototype.toString.call(this);
+ }
+ return string;
+ },
+
+ CLASS_NAME: "OpenLayers.Filter"
+});
+/* ======================================================================
+ OpenLayers/Filter/FeatureId.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.FeatureId
+ * This class represents a ogc:FeatureId Filter, as being used for rule-based SLD
+ * styling
+ *
+ * Inherits from:
+ * - <OpenLayers.Filter>
+ */
+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:
+ * {<OpenLayers.Filter.FeatureId>}
+ */
+ initialize: function(options) {
+ this.fids = [];
+ OpenLayers.Filter.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: evaluate
+ * evaluates this rule for a specific feature
+ *
+ * Parameters:
+ * feature - {<OpenLayers.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<len; i++) {
+ var fid = feature.fid || feature.id;
+ if (fid == this.fids[i]) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.FeatureId>} Clone of this filter.
+ */
+ clone: function() {
+ var filter = new OpenLayers.Filter.FeatureId();
+ OpenLayers.Util.extend(filter, this);
+ filter.fids = this.fids.slice();
+ return filter;
+ },
+
+ CLASS_NAME: "OpenLayers.Filter.FeatureId"
+});
+/* ======================================================================
+ OpenLayers/Filter/Logical.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.Logical
+ * This class represents ogc:And, ogc:Or and ogc:Not rules.
+ *
+ * Inherits from:
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.Logical = OpenLayers.Class(OpenLayers.Filter, {
+
+ /**
+ * APIProperty: filters
+ * {Array(<OpenLayers.Filter>)} 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:
+ * {<OpenLayers.Filter.Logical>}
+ */
+ 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<len; i++) {
+ if (this.filters[i].evaluate(context) == false) {
+ return false;
+ }
+ }
+ return true;
+
+ case OpenLayers.Filter.Logical.OR:
+ for (i=0, len=this.filters.length; i<len; i++) {
+ if (this.filters[i].evaluate(context) == true) {
+ return true;
+ }
+ }
+ return false;
+
+ case OpenLayers.Filter.Logical.NOT:
+ return (!this.filters[0].evaluate(context));
+ }
+ return undefined;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Logical>} Clone of this filter.
+ */
+ clone: function() {
+ var filters = [];
+ for(var i=0, len=this.filters.length; i<len; ++i) {
+ filters.push(this.filters[i].clone());
+ }
+ return new OpenLayers.Filter.Logical({
+ type: this.type,
+ filters: filters
+ });
+ },
+
+ CLASS_NAME: "OpenLayers.Filter.Logical"
+});
+
+
+OpenLayers.Filter.Logical.AND = "&&";
+OpenLayers.Filter.Logical.OR = "||";
+OpenLayers.Filter.Logical.NOT = "!";
+/* ======================================================================
+ OpenLayers/Filter/Comparison.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.Comparison
+ * This class represents a comparison filter.
+ *
+ * Inherits from:
+ * - <OpenLayers.Filter>
+ */
+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:
+ * {<OpenLayers.Filter.Comparison>}
+ */
+ 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 <value> 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:
+ * {<OpenLayers.Filter.Comparison>} 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 <OpenLayers.Format.Filter>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+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 - {<OpenLayers.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:
+ * {<OpenLayers.Filter>} A filter object.
+ */
+
+ CLASS_NAME: "OpenLayers.Format.Filter"
+});
+/* ======================================================================
+ OpenLayers/Format/WFST.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format.js
+ */
+
+/**
+ * Function: OpenLayers.Format.WFST
+ * Used to create a versioned WFS protocol. Default version is 1.0.0.
+ *
+ * Returns:
+ * {<OpenLayers.Format>} A WFST format of the given version.
+ */
+OpenLayers.Format.WFST = function(options) {
+ options = OpenLayers.Util.applyDefaults(
+ options, OpenLayers.Format.WFST.DEFAULTS
+ );
+ var cls = OpenLayers.Format.WFST["v"+options.version.replace(/\./g, "_")];
+ if(!cls) {
+ throw "Unsupported WFST version: " + options.version;
+ }
+ return new cls(options);
+};
+
+/**
+ * Constant: OpenLayers.Format.WFST.DEFAULTS
+ * {Object} Default properties for the WFST format.
+ */
+OpenLayers.Format.WFST.DEFAULTS = {
+ "version": "1.0.0"
+};
+/* ======================================================================
+ OpenLayers/Filter/Spatial.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.Spatial
+ * This class represents a spatial filter.
+ * Currently implemented: BBOX, DWithin and Intersects
+ *
+ * Inherits from:
+ * - <OpenLayers.Filter>
+ */
+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
+ * {<OpenLayers.Bounds> || <OpenLayers.Geometry>} 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:
+ * {<OpenLayers.Filter.Spatial>}
+ */
+
+ /**
+ * Method: evaluate
+ * Evaluates this filter for a specific feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} 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:
+ * {<OpenLayers.Filter.Spatial>} Clone of this filter.
+ */
+ clone: function() {
+ var options = OpenLayers.Util.applyDefaults({
+ value: this.value && this.value.clone && this.value.clone()
+ }, this);
+ return new OpenLayers.Filter.Spatial(options);
+ },
+ CLASS_NAME: "OpenLayers.Filter.Spatial"
+});
+
+OpenLayers.Filter.Spatial.BBOX = "BBOX";
+OpenLayers.Filter.Spatial.INTERSECTS = "INTERSECTS";
+OpenLayers.Filter.Spatial.DWITHIN = "DWITHIN";
+OpenLayers.Filter.Spatial.WITHIN = "WITHIN";
+OpenLayers.Filter.Spatial.CONTAINS = "CONTAINS";
+/* ======================================================================
+ OpenLayers/Format/WFST/v1.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/WFST.js
+ * @requires OpenLayers/Filter/Spatial.js
+ * @requires OpenLayers/Filter/FeatureId.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WFST.v1
+ * Superclass for WFST parsers.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+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
+ * <OpenLayers.Format.WFST.v1_0_0> or <OpenLayers.Format.WFST.v1_1_0>
+ * 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(<OpenLayers.Feature.Vector>)} 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<len; i++) {
+ options.featureType = this.featureType[i];
+ this.writeNode("Query", options, node);
+ }
+ }
+ return node;
+ },
+ "Transaction": function(obj) {
+ obj = obj || {};
+ var options = obj.options || {};
+ var node = this.createElementNSPlus("wfs:Transaction", {
+ attributes: {
+ service: "WFS",
+ version: this.version,
+ handle: options.handle
+ }
+ });
+ var i, len;
+ var features = obj.features;
+ if(features) {
+ // temporarily re-assigning geometry types
+ if (options.multi === true) {
+ OpenLayers.Util.extend(this.geometryTypes, {
+ "OpenLayers.Geometry.Point": "MultiPoint",
+ "OpenLayers.Geometry.LineString": (this.multiCurve === true) ? "MultiCurve": "MultiLineString",
+ "OpenLayers.Geometry.Polygon": (this.multiSurface === true) ? "MultiSurface" : "MultiPolygon"
+ });
+ }
+ var name, feature;
+ for(i=0, len=features.length; i<len; ++i) {
+ feature = features[i];
+ name = this.stateName[feature.state];
+ if(name) {
+ this.writeNode(name, {
+ feature: feature,
+ options: options
+ }, node);
+ }
+ }
+ // switch back to original geometry types assignment
+ if (options.multi === true) {
+ this.setGeometryTypes();
+ }
+ }
+ if (options.nativeElements) {
+ for (i=0, len=options.nativeElements.length; i<len; ++i) {
+ this.writeNode("wfs:Native",
+ options.nativeElements[i], node);
+ }
+ }
+ return node;
+ },
+ "Native": function(nativeElement) {
+ var node = this.createElementNSPlus("wfs:Native", {
+ attributes: {
+ vendorId: nativeElement.vendorId,
+ safeToIgnore: nativeElement.safeToIgnore
+ },
+ value: nativeElement.value
+ });
+ return node;
+ },
+ "Insert": function(obj) {
+ var feature = obj.feature;
+ var options = obj.options;
+ var node = this.createElementNSPlus("wfs:Insert", {
+ attributes: {
+ handle: options && options.handle
+ }
+ });
+ this.srsName = this.getSrsName(feature);
+ this.writeNode("feature:_typeName", feature, node);
+ return node;
+ },
+ "Update": function(obj) {
+ var feature = obj.feature;
+ var options = obj.options;
+ var node = this.createElementNSPlus("wfs:Update", {
+ attributes: {
+ handle: options && options.handle,
+ typeName: (this.featureNS ? this.featurePrefix + ":" : "") +
+ this.featureType
+ }
+ });
+ if(this.featureNS) {
+ node.setAttribute("xmlns:" + this.featurePrefix, this.featureNS);
+ }
+
+ // add in geometry
+ var modified = feature.modified;
+ if (this.geometryName !== null && (!modified || modified.geometry !== undefined)) {
+ this.srsName = this.getSrsName(feature);
+ this.writeNode(
+ "Property", {name: this.geometryName, value: feature.geometry}, node
+ );
+ }
+
+ // add in attributes
+ for(var key in feature.attributes) {
+ if(feature.attributes[key] !== undefined &&
+ (!modified || !modified.attributes ||
+ (modified.attributes && modified.attributes[key] !== undefined))) {
+ this.writeNode(
+ "Property", {name: key, value: feature.attributes[key]}, node
+ );
+ }
+ }
+
+ // add feature id filter
+ this.writeNode("ogc:Filter", new OpenLayers.Filter.FeatureId({
+ fids: [feature.fid]
+ }), node);
+
+ return node;
+ },
+ "Property": function(obj) {
+ var node = this.createElementNSPlus("wfs:Property");
+ this.writeNode("Name", obj.name, node);
+ if(obj.value !== null) {
+ this.writeNode("Value", obj.value, node);
+ }
+ return node;
+ },
+ "Name": function(name) {
+ return this.createElementNSPlus("wfs:Name", {value: name});
+ },
+ "Value": function(obj) {
+ var node;
+ if(obj instanceof OpenLayers.Geometry) {
+ node = this.createElementNSPlus("wfs:Value");
+ var geom = this.writeNode("feature:_geometry", obj).firstChild;
+ node.appendChild(geom);
+ } else {
+ node = this.createElementNSPlus("wfs:Value", {value: obj});
+ }
+ return node;
+ },
+ "Delete": function(obj) {
+ var feature = obj.feature;
+ var options = obj.options;
+ var node = this.createElementNSPlus("wfs:Delete", {
+ attributes: {
+ handle: options && options.handle,
+ typeName: (this.featureNS ? this.featurePrefix + ":" : "") +
+ this.featureType
+ }
+ });
+ if(this.featureNS) {
+ node.setAttribute("xmlns:" + this.featurePrefix, this.featureNS);
+ }
+ this.writeNode("ogc:Filter", new OpenLayers.Filter.FeatureId({
+ fids: [feature.fid]
+ }), node);
+ return node;
+ }
+ }
+ },
+
+ /**
+ * Method: schemaLocationAttr
+ * Generate the xsi:schemaLocation attribute value.
+ *
+ * Returns:
+ * {String} The xsi:schemaLocation attribute or undefined if none.
+ */
+ schemaLocationAttr: function(options) {
+ options = OpenLayers.Util.extend({
+ featurePrefix: this.featurePrefix,
+ schema: this.schema
+ }, options);
+ var schemaLocations = OpenLayers.Util.extend({}, this.schemaLocations);
+ if(options.schema) {
+ schemaLocations[options.featurePrefix] = options.schema;
+ }
+ var parts = [];
+ var uri;
+ for(var key in schemaLocations) {
+ uri = this.namespaces[key];
+ if(uri) {
+ parts.push(uri + " " + schemaLocations[key]);
+ }
+ }
+ var value = parts.join(" ") || undefined;
+ return value;
+ },
+
+ /**
+ * Method: setFilterProperty
+ * Set the property of each spatial filter.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>}
+ */
+ setFilterProperty: function(filter) {
+ if(filter.filters) {
+ for(var i=0, len=filter.filters.length; i<len; ++i) {
+ OpenLayers.Format.WFST.v1.prototype.setFilterProperty.call(this, filter.filters[i]);
+ }
+ } else {
+ if(filter instanceof OpenLayers.Filter.Spatial && !filter.property) {
+ // got a spatial filter without property, so set it
+ filter.property = this.geometryName;
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WFST.v1"
+
+});
+/* ======================================================================
+ OpenLayers/Geometry/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/Geometry/Collection.js
+ * @requires OpenLayers/Geometry/LinearRing.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Polygon
+ * Polygon is a collection of Geometry.LinearRings.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Collection>
+ * - <OpenLayers.Geometry>
+ */
+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(<OpenLayers.Geometry.LinearRing>)}
+ */
+
+ /**
+ * 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<len; i++) {
+ area -= Math.abs(this.components[i].getArea());
+ }
+ }
+ return area;
+ },
+
+ /**
+ * APIMethod: getGeodesicArea
+ * Calculate the approximate area of the polygon were it projected onto
+ * the earth.
+ *
+ * Parameters:
+ * projection - {<OpenLayers.Projection>} 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<len; i++) {
+ area -= Math.abs(this.components[i].getGeodesicArea(projection));
+ }
+ }
+ return area;
+ },
+
+ /**
+ * Method: containsPoint
+ * Test if a point is inside a polygon. Points on a polygon edge are
+ * considered inside.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * 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<numRings; ++i) {
+ hole = this.components[i].containsPoint(point);
+ if(hole) {
+ if(hole === 1) {
+ // on edge
+ contained = 1;
+ } else {
+ // in hole
+ contained = false;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ return contained;
+ },
+
+ /**
+ * APIMethod: intersects
+ * Determine if the input geometry intersects this one.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} 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<len; ++i) {
+ intersect = geometry.intersects(this.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ if(!intersect) {
+ // check if this poly contains points of the ring/linestring
+ for(i=0, len=geometry.components.length; i<len; ++i) {
+ intersect = this.containsPoint(geometry.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ }
+ } else {
+ for(i=0, len=geometry.components.length; i<len; ++ i) {
+ intersect = this.intersects(geometry.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ }
+ // check case where this poly is wholly contained by another
+ if(!intersect && geometry.CLASS_NAME == "OpenLayers.Geometry.Polygon") {
+ // exterior ring points will be contained in the other geometry
+ var ring = this.components[0];
+ for(i=0, len=ring.components.length; i<len; ++i) {
+ intersect = geometry.containsPoint(ring.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ }
+ return intersect;
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Calculate the closest distance between two geometries (on the x-y plane).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.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 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 - {<OpenLayers.Geometry.Point>} 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<sides; ++i) {
+ rotatedAngle = angle + (i * 2 * Math.PI / sides);
+ x = origin.x + (radius * Math.cos(rotatedAngle));
+ y = origin.y + (radius * Math.sin(rotatedAngle));
+ points.push(new OpenLayers.Geometry.Point(x, y));
+ }
+ var ring = new OpenLayers.Geometry.LinearRing(points);
+ return new OpenLayers.Geometry.Polygon([ring]);
+};
+/* ======================================================================
+ OpenLayers/Geometry/MultiPolygon.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/Polygon.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.MultiPolygon
+ * MultiPolygon is a geometry with multiple <OpenLayers.Geometry.Polygon>
+ * components. Create a new instance with the <OpenLayers.Geometry.MultiPolygon>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Collection>
+ */
+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(<OpenLayers.Geometry.Polygon>)} 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 <OpenLayers.Format.GML>
+ * constructor. Supports the GML simple features profile.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+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(<OpenLayers.Feature.Vector>)} 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<featureNodes.length; i++) {
+ var feature = this.parseFeature(featureNodes[i]);
+ if(feature) {
+ features.push(feature);
+ }
+ }
+ return features;
+ },
+
+ /**
+ * Method: parseFeature
+ * This function is the core of the GML parsing code in OpenLayers.
+ * It creates the geometries that are then attached to the returned
+ * feature, and calls parseAttributes() to get attribute data out.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML feature node.
+ */
+ parseFeature: function(node) {
+ // only accept one geometry per feature - look for highest "order"
+ var order = ["MultiPolygon", "Polygon",
+ "MultiLineString", "LineString",
+ "MultiPoint", "Point", "Envelope"];
+ // FIXME: In case we parse a feature with no geometry, but boundedBy an Envelope,
+ // this code creates a geometry derived from the Envelope. This is not correct.
+ var type, nodeList, geometry, parser;
+ for(var i=0; i<order.length; ++i) {
+ type = order[i];
+ nodeList = this.getElementsByTagNameNS(node, this.gmlns, type);
+ if(nodeList.length > 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<boxNodes.length; ++i) {
+ var boxNode = boxNodes[i];
+ var box = this.parseGeometry["box"].apply(this, [boxNode]);
+ var parentNode = boxNode.parentNode;
+ var parentName = parentNode.localName ||
+ parentNode.nodeName.split(":").pop();
+ if(parentName === "boundedBy") {
+ bounds = box;
+ } else {
+ geometry = box.toGeometry();
+ }
+ }
+
+ // construct feature (optionally with attributes)
+ var attributes;
+ if(this.extractAttributes) {
+ attributes = this.parseAttributes(node);
+ }
+ var feature = new OpenLayers.Feature.Vector(geometry, attributes);
+ feature.bounds = bounds;
+
+ feature.gml = {
+ featureType: node.firstChild.nodeName.split(":")[1],
+ featureNS: node.firstChild.namespaceURI,
+ featureNSPrefix: node.firstChild.prefix
+ };
+
+ // assign fid - this can come from a "fid" or "id" attribute
+ var childNode = node.firstChild;
+ var fid;
+ while(childNode) {
+ if(childNode.nodeType == 1) {
+ fid = childNode.getAttribute("fid") ||
+ childNode.getAttribute("id");
+ if(fid) {
+ break;
+ }
+ }
+ childNode = childNode.nextSibling;
+ }
+ feature.fid = fid;
+ return feature;
+ },
+
+ /**
+ * Property: parseGeometry
+ * Properties of this object are the functions that parse geometries based
+ * on their type.
+ */
+ parseGeometry: {
+
+ /**
+ * Method: parseGeometry.point
+ * Given a GML node representing a point geometry, create an OpenLayers
+ * point geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} A point geometry.
+ */
+ point: function(node) {
+ /**
+ * Three coordinate variations to consider:
+ * 1) <gml:pos>x y z</gml:pos>
+ * 2) <gml:coordinates>x, y, z</gml:coordinates>
+ * 3) <gml:coord><gml:X>x</gml:X><gml:Y>y</gml:Y></gml:coord>
+ */
+ var nodeList, coordString;
+ var coords = [];
+
+ // look for <gml:pos>
+ 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 <gml:coordinates>
+ 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 <gml:coord>
+ 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:
+ * {<OpenLayers.Geometry.MultiPoint>} 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<nodeList.length; ++i) {
+ point = this.parseGeometry.point.apply(this, [nodeList[i]]);
+ if(point) {
+ components.push(point);
+ }
+ }
+ }
+ return new OpenLayers.Geometry.MultiPoint(components);
+ },
+
+ /**
+ * Method: parseGeometry.linestring
+ * Given a GML node representing a linestring geometry, create an
+ * OpenLayers linestring geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.LineString>} A linestring geometry.
+ */
+ linestring: function(node, ring) {
+ /**
+ * Two coordinate variations to consider:
+ * 1) <gml:posList dimension="d">x0 y0 z0 x1 y1 z1</gml:posList>
+ * 2) <gml:coordinates>x0, y0, z0 x1, y1, z1</gml:coordinates>
+ */
+ var nodeList, coordString;
+ var coords = [];
+ var points = [];
+
+ // look for <gml:posList>
+ 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<coords.length/dim; ++i) {
+ j = i * dim;
+ x = coords[j];
+ y = coords[j+1];
+ z = (dim == 2) ? null : coords[j+2];
+ if (this.xy) {
+ points.push(new OpenLayers.Geometry.Point(x, y, z));
+ } else {
+ points.push(new OpenLayers.Geometry.Point(y, x, z));
+ }
+ }
+ }
+
+ // look for <gml:coordinates>
+ 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<pointList.length; ++i) {
+ coords = pointList[i].split(",");
+ if(coords.length == 2) {
+ coords[2] = null;
+ }
+ if (this.xy) {
+ points.push(new OpenLayers.Geometry.Point(coords[0],
+ coords[1],
+ coords[2]));
+ } else {
+ points.push(new OpenLayers.Geometry.Point(coords[1],
+ coords[0],
+ coords[2]));
+ }
+ }
+ }
+ }
+
+ var line = null;
+ if(points.length != 0) {
+ if(ring) {
+ line = new OpenLayers.Geometry.LinearRing(points);
+ } else {
+ line = new OpenLayers.Geometry.LineString(points);
+ }
+ }
+ return line;
+ },
+
+ /**
+ * Method: parseGeometry.multilinestring
+ * Given a GML node representing a multilinestring geometry, create an
+ * OpenLayers multilinestring geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.MultiLineString>} 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<nodeList.length; ++i) {
+ line = this.parseGeometry.linestring.apply(this,
+ [nodeList[i]]);
+ if(line) {
+ components.push(line);
+ }
+ }
+ }
+ return new OpenLayers.Geometry.MultiLineString(components);
+ },
+
+ /**
+ * Method: parseGeometry.polygon
+ * Given a GML node representing a polygon geometry, create an
+ * OpenLayers polygon geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Polygon>} 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<nodeList.length; ++i) {
+ ring = this.parseGeometry.linestring.apply(this,
+ [nodeList[i], true]);
+ if(ring) {
+ components.push(ring);
+ }
+ }
+ }
+ return new OpenLayers.Geometry.Polygon(components);
+ },
+
+ /**
+ * Method: parseGeometry.multipolygon
+ * Given a GML node representing a multipolygon geometry, create an
+ * OpenLayers multipolygon geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.MultiPolygon>} 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<nodeList.length; ++i) {
+ polygon = this.parseGeometry.polygon.apply(this,
+ [nodeList[i]]);
+ if(polygon) {
+ components.push(polygon);
+ }
+ }
+ }
+ return new OpenLayers.Geometry.MultiPolygon(components);
+ },
+
+ envelope: function(node) {
+ var components = [];
+ var coordString;
+ var envelope;
+
+ var lpoint = this.getElementsByTagNameNS(node, this.gmlns, "lowerCorner");
+ if (lpoint.length > 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:
+ * {<OpenLayers.Bounds>} 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<children.length; ++i) {
+ child = children[i];
+ if(child.nodeType == 1) {
+ grandchildren = child.childNodes;
+ if(grandchildren.length == 1) {
+ grandchild = grandchildren[0];
+ if(grandchild.nodeType == 3 ||
+ grandchild.nodeType == 4) {
+ name = (child.prefix) ?
+ child.nodeName.split(":")[1] :
+ child.nodeName;
+ value = grandchild.nodeValue.replace(
+ this.regExes.trimSpace, "");
+ attributes[name] = value;
+ }
+ } else {
+ // If child has no childNodes (grandchildren),
+ // set an attribute with null value.
+ // e.g. <prefix:fieldname/> 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(<OpenLayers.Feature.Vector>)} 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<features.length; i++) {
+ gml.appendChild(this.createFeatureXML(features[i]));
+ }
+ return OpenLayers.Format.XML.prototype.write.apply(this, [gml]);
+ },
+
+ /**
+ * Method: createFeatureXML
+ * Accept an OpenLayers.Feature.Vector, and build a GML node for it.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} 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 - {<OpenLayers.Geometry.Point>} 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 - {<OpenLayers.Geometry.MultiPoint>} 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<points.length; i++) {
+ pointMember = this.createElementNS(this.gmlns,
+ "gml:pointMember");
+ pointGeom = this.buildGeometry.point.apply(this,
+ [points[i]]);
+ pointMember.appendChild(pointGeom);
+ gml.appendChild(pointMember);
+ }
+ return gml;
+ },
+
+ /**
+ * Method: buildGeometry.linestring
+ * Given an OpenLayers linestring geometry, create a GML linestring.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.LineString>} 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 - {<OpenLayers.Geometry.MultiLineString>} 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<lines.length; ++i) {
+ lineMember = this.createElementNS(this.gmlns,
+ "gml:lineStringMember");
+ lineGeom = this.buildGeometry.linestring.apply(this,
+ [lines[i]]);
+ lineMember.appendChild(lineGeom);
+ gml.appendChild(lineMember);
+ }
+ return gml;
+ },
+
+ /**
+ * Method: buildGeometry.linearring
+ * Given an OpenLayers linearring geometry, create a GML linearring.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.LinearRing>} 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 - {<OpenLayers.Geometry.Polygon>} 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<rings.length; ++i) {
+ type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
+ ringMember = this.createElementNS(this.gmlns,
+ "gml:" + type);
+ ringGeom = this.buildGeometry.linearring.apply(this,
+ [rings[i]]);
+ ringMember.appendChild(ringGeom);
+ gml.appendChild(ringMember);
+ }
+ return gml;
+ },
+
+ /**
+ * Method: buildGeometry.multipolygon
+ * Given an OpenLayers multipolygon geometry, create a GML multipolygon.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.MultiPolygon>} 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<polys.length; ++i) {
+ polyMember = this.createElementNS(this.gmlns,
+ "gml:polygonMember");
+ polyGeom = this.buildGeometry.polygon.apply(this,
+ [polys[i]]);
+ polyMember.appendChild(polyGeom);
+ gml.appendChild(polyMember);
+ }
+ return gml;
+
+ },
+
+ /**
+ * Method: buildGeometry.bounds
+ * Given an OpenLayers bounds, create a GML box.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Geometry.Bounds>} 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)
+ * <gml:coordinates decimal="." cs="," ts=" ">...</gml:coordinates>
+ * (end)
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.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<points.length; i++) {
+ parts.push(points[i].x + "," + points[i].y);
+ }
+ }
+
+ var txtNode = this.createTextNode(parts.join(" "));
+ coordinatesNode.appendChild(txtNode);
+
+ return coordinatesNode;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.GML"
+});
+/* ======================================================================
+ OpenLayers/Format/GML/Base.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/GML.js
+ */
+
+/**
+ * Though required in the full build, if the GML format is excluded, we set
+ * the namespace here.
+ */
+if(!OpenLayers.Format.GML) {
+ OpenLayers.Format.GML = {};
+}
+
+/**
+ * Class: OpenLayers.Format.GML.Base
+ * Superclass for GML parsers.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+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 <read> 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 <setGeometryTypes> 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 <featureNS>,
+ * but auto-configured <featureNS> and <featureType> during read.
+ * Subclasses making use of <featureType> auto-configuration should make
+ * the first call to the <readNode> 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
+ * <OpenLayers.Format.GML.v2> or <OpenLayers.Format.GML.v3> 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(<OpenLayers.Feature.Vector>)} 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<len; ++i) {
+ this.readNode(elements[i], {features: features}, true);
+ }
+ } else {
+ // look for gml:featureMembers elements (this is v3, but does no harm here)
+ var elements = this.getElementsByTagNameNS(
+ data, this.namespaces.gml, "featureMembers"
+ );
+ if(elements.length) {
+ // there can be only one
+ this.readNode(elements[0], {features: features}, true);
+ }
+ }
+ }
+ return features;
+ },
+
+ /**
+ * 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) {
+ // on subsequent calls of format.read(), we want to reset auto-
+ // configured properties and auto-configure again.
+ if (first === true && this.autoConfig === true) {
+ this.featureType = null;
+ delete this.namespaceAlias[this.featureNS];
+ delete this.namespaces["feature"];
+ this.featureNS = null;
+ }
+ // featureType auto-configuration
+ if (!this.featureNS && (!(node.prefix in this.namespaces) &&
+ node.parentNode.namespaceURI == this.namespaces["gml"] &&
+ this.regExes.featureMember.test(node.parentNode.nodeName))) {
+ this.featureType = node.nodeName.split(":").pop();
+ this.setNamespace("feature", node.namespaceURI);
+ this.featureNS = node.namespaceURI;
+ this.autoConfig = true;
+ }
+ return OpenLayers.Format.XML.prototype.readNode.apply(this, [node, 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: {
+ "gml": {
+ "_inherit": function(node, obj, container) {
+ // To be implemented by version specific parsers
+ },
+ "featureMember": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "featureMembers": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "name": function(node, obj) {
+ obj.name = this.getChildValue(node);
+ },
+ "boundedBy": function(node, obj) {
+ var container = {};
+ this.readChildNodes(node, container);
+ if(container.components && container.components.length > 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<numPoints; ++i) {
+ coords = pointList[i].split(",");
+ if (this.xy) {
+ points[i] = new OpenLayers.Geometry.Point(
+ coords[0], coords[1], coords[2]
+ );
+ } else {
+ points[i] = new OpenLayers.Geometry.Point(
+ coords[1], coords[0], coords[2]
+ );
+ }
+ }
+ obj.points = points;
+ },
+ "coord": function(node, obj) {
+ var coord = {};
+ this.readChildNodes(node, coord);
+ if(!obj.points) {
+ obj.points = [];
+ }
+ obj.points.push(new OpenLayers.Geometry.Point(
+ coord.x, coord.y, coord.z
+ ));
+ },
+ "X": function(node, coord) {
+ coord.x = this.getChildValue(node);
+ },
+ "Y": function(node, coord) {
+ coord.y = this.getChildValue(node);
+ },
+ "Z": function(node, coord) {
+ coord.z = this.getChildValue(node);
+ },
+ "MultiPoint": function(node, container) {
+ var obj = {components: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ container.components = [
+ new OpenLayers.Geometry.MultiPoint(obj.components)
+ ];
+ },
+ "pointMember": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "LineString": function(node, container) {
+ var obj = {};
+ 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)
+ );
+ },
+ "MultiLineString": function(node, container) {
+ var obj = {components: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ container.components = [
+ new OpenLayers.Geometry.MultiLineString(obj.components)
+ ];
+ },
+ "lineStringMember": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Polygon": function(node, container) {
+ var obj = {outer: null, inner: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ obj.inner.unshift(obj.outer);
+ if(!container.components) {
+ container.components = [];
+ }
+ container.components.push(
+ new OpenLayers.Geometry.Polygon(obj.inner)
+ );
+ },
+ "LinearRing": function(node, obj) {
+ var container = {};
+ this.readers.gml._inherit.apply(this, [node, container]);
+ this.readChildNodes(node, container);
+ obj.components = [new OpenLayers.Geometry.LinearRing(
+ container.points
+ )];
+ },
+ "MultiPolygon": function(node, container) {
+ var obj = {components: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ container.components = [
+ new OpenLayers.Geometry.MultiPolygon(obj.components)
+ ];
+ },
+ "polygonMember": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "GeometryCollection": function(node, container) {
+ var obj = {components: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ container.components = [
+ new OpenLayers.Geometry.Collection(obj.components)
+ ];
+ },
+ "geometryMember": function(node, obj) {
+ this.readChildNodes(node, obj);
+ }
+ },
+ "feature": {
+ "*": function(node, obj) {
+ // The node can either be named like the featureType, or it
+ // can be a child of the feature:featureType. Children can be
+ // geometry or attributes.
+ var name;
+ var local = node.localName || node.nodeName.split(":").pop();
+ // Since an attribute can have the same name as the feature type
+ // we only want to read the node as a feature if the parent
+ // node can have feature nodes as children. In this case, the
+ // obj.features property is set.
+ if (obj.features) {
+ if (!this.singleFeatureType &&
+ (OpenLayers.Util.indexOf(this.featureType, local) !== -1)) {
+ name = "_typeName";
+ } else if(local === this.featureType) {
+ name = "_typeName";
+ }
+ } else {
+ // Assume attribute elements have one child node and that the child
+ // is a text node. Otherwise assume it is a geometry node.
+ if(node.childNodes.length == 0 ||
+ (node.childNodes.length == 1 && node.firstChild.nodeType == 3)) {
+ if(this.extractAttributes) {
+ name = "_attribute";
+ }
+ } else {
+ name = "_geometry";
+ }
+ }
+ if(name) {
+ this.readers.feature[name].apply(this, [node, obj]);
+ }
+ },
+ "_typeName": function(node, obj) {
+ var container = {components: [], attributes: {}};
+ this.readChildNodes(node, container);
+ // look for common gml namespaced elements
+ if(container.name) {
+ container.attributes.name = container.name;
+ }
+ var feature = new OpenLayers.Feature.Vector(
+ container.components[0], container.attributes
+ );
+ if (!this.singleFeatureType) {
+ feature.type = node.nodeName.split(":").pop();
+ feature.namespace = node.namespaceURI;
+ }
+ var fid = node.getAttribute("fid") ||
+ this.getAttributeNS(node, this.namespaces["gml"], "id");
+ if(fid) {
+ feature.fid = fid;
+ }
+ if(this.internalProjection && this.externalProjection &&
+ feature.geometry) {
+ feature.geometry.transform(
+ this.externalProjection, this.internalProjection
+ );
+ }
+ if(container.bounds) {
+ feature.bounds = container.bounds;
+ }
+ obj.features.push(feature);
+ },
+ "_geometry": function(node, obj) {
+ if (!this.geometryName) {
+ this.geometryName = node.nodeName.split(":").pop();
+ }
+ this.readChildNodes(node, obj);
+ },
+ "_attribute": function(node, obj) {
+ var local = node.localName || node.nodeName.split(":").pop();
+ var value = this.getChildValue(node);
+ obj.attributes[local] = value;
+ }
+ },
+ "wfs": {
+ "FeatureCollection": function(node, obj) {
+ this.readChildNodes(node, obj);
+ }
+ }
+ },
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>) | 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<ii; ++i) {
+ this.writeNode("pointMember", components[i], node);
+ }
+ return node;
+ },
+ "pointMember": function(geometry) {
+ var node = this.createElementNSPlus("gml:pointMember");
+ this.writeNode("Point", geometry, node);
+ return node;
+ },
+ "MultiLineString": function(geometry) {
+ var node = this.createElementNSPlus("gml:MultiLineString");
+ var components = geometry.components || [geometry];
+ for(var i=0, ii=components.length; i<ii; ++i) {
+ this.writeNode("lineStringMember", components[i], node);
+ }
+ return node;
+ },
+ "lineStringMember": function(geometry) {
+ var node = this.createElementNSPlus("gml:lineStringMember");
+ this.writeNode("LineString", geometry, node);
+ return node;
+ },
+ "MultiPolygon": function(geometry) {
+ var node = this.createElementNSPlus("gml:MultiPolygon");
+ var components = geometry.components || [geometry];
+ for(var i=0, ii=components.length; i<ii; ++i) {
+ this.writeNode(
+ "polygonMember", components[i], node
+ );
+ }
+ return node;
+ },
+ "polygonMember": function(geometry) {
+ var node = this.createElementNSPlus("gml:polygonMember");
+ this.writeNode("Polygon", geometry, node);
+ return node;
+ },
+ "GeometryCollection": function(geometry) {
+ var node = this.createElementNSPlus("gml:GeometryCollection");
+ for(var i=0, len=geometry.components.length; i<len; ++i) {
+ this.writeNode("geometryMember", geometry.components[i], node);
+ }
+ return node;
+ },
+ "geometryMember": function(geometry) {
+ var node = this.createElementNSPlus("gml:geometryMember");
+ var child = this.writeNode("feature:_geometry", geometry);
+ node.appendChild(child.firstChild);
+ return node;
+ }
+ },
+ "feature": {
+ "_typeName": function(feature) {
+ var node = this.createElementNSPlus("feature:" + this.featureType, {
+ attributes: {fid: feature.fid}
+ });
+ if(feature.geometry) {
+ this.writeNode("feature:_geometry", feature.geometry, node);
+ }
+ for(var name in feature.attributes) {
+ var value = feature.attributes[name];
+ if(value != null) {
+ this.writeNode(
+ "feature:_attribute",
+ {name: name, value: value}, node
+ );
+ }
+ }
+ return node;
+ },
+ "_geometry": function(geometry) {
+ if(this.externalProjection && this.internalProjection) {
+ geometry = geometry.clone().transform(
+ this.internalProjection, this.externalProjection
+ );
+ }
+ var node = this.createElementNSPlus(
+ "feature:" + this.geometryName
+ );
+ var type = this.geometryTypes[geometry.CLASS_NAME];
+ var child = this.writeNode("gml:" + type, geometry, node);
+ if(this.srsName) {
+ child.setAttribute("srsName", this.srsName);
+ }
+ return node;
+ },
+ "_attribute": function(obj) {
+ return this.createElementNSPlus("feature:" + obj.name, {
+ value: obj.value
+ });
+ }
+ },
+ "wfs": {
+ "FeatureCollection": function(features) {
+ /**
+ * This is only here because GML2 only describes abstract
+ * feature collections. Typically, you would not be using
+ * the GML format to write wfs elements. This just provides
+ * some way to write out lists of features. GML3 defines the
+ * featureMembers element, so that is used by default instead.
+ */
+ var node = this.createElementNSPlus("wfs:FeatureCollection");
+ for(var i=0, len=features.length; i<len; ++i) {
+ this.writeNode("gml:featureMember", features[i], node);
+ }
+ return node;
+ }
+ }
+ },
+
+ /**
+ * Method: setGeometryTypes
+ * Sets the <geometryTypes> mapping.
+ */
+ setGeometryTypes: function() {
+ this.geometryTypes = {
+ "OpenLayers.Geometry.Point": "Point",
+ "OpenLayers.Geometry.MultiPoint": "MultiPoint",
+ "OpenLayers.Geometry.LineString": "LineString",
+ "OpenLayers.Geometry.MultiLineString": "MultiLineString",
+ "OpenLayers.Geometry.Polygon": "Polygon",
+ "OpenLayers.Geometry.MultiPolygon": "MultiPolygon",
+ "OpenLayers.Geometry.Collection": "GeometryCollection"
+ };
+ },
+
+ CLASS_NAME: "OpenLayers.Format.GML.Base"
+
+});
+/* ======================================================================
+ OpenLayers/Format/GML/v2.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/GML/Base.js
+ */
+
+/**
+ * Class: OpenLayers.Format.GML.v2
+ * Parses GML version 2.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.GML.Base>
+ */
+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>) | 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<numPoints; ++i) {
+ point = points[i];
+ if(this.xy) {
+ parts[i] = point.x + "," + point.y;
+ } else {
+ parts[i] = point.y + "," + point.x;
+ }
+ if(point.z != undefined) { // allow null or undefined
+ parts[i] += "," + point.z;
+ }
+ }
+ return this.createElementNSPlus("gml:coordinates", {
+ attributes: {
+ decimal: ".", cs: ",", ts: " "
+ },
+ value: (numPoints == 1) ? parts[0] : parts.join(" ")
+ });
+ },
+ "LineString": function(geometry) {
+ var node = this.createElementNSPlus("gml:LineString");
+ this.writeNode("coordinates", geometry.components, node);
+ return node;
+ },
+ "Polygon": function(geometry) {
+ var node = this.createElementNSPlus("gml:Polygon");
+ this.writeNode("outerBoundaryIs", geometry.components[0], node);
+ for(var i=1; i<geometry.components.length; ++i) {
+ this.writeNode(
+ "innerBoundaryIs", geometry.components[i], node
+ );
+ }
+ return node;
+ },
+ "outerBoundaryIs": function(ring) {
+ var node = this.createElementNSPlus("gml:outerBoundaryIs");
+ this.writeNode("LinearRing", ring, node);
+ return node;
+ },
+ "innerBoundaryIs": function(ring) {
+ var node = this.createElementNSPlus("gml:innerBoundaryIs");
+ this.writeNode("LinearRing", ring, node);
+ return node;
+ },
+ "LinearRing": function(ring) {
+ var node = this.createElementNSPlus("gml:LinearRing");
+ this.writeNode("coordinates", ring.components, node);
+ return node;
+ },
+ "Box": function(bounds) {
+ var node = this.createElementNSPlus("gml:Box");
+ this.writeNode("coordinates", [
+ {x: bounds.left, y: bounds.bottom},
+ {x: bounds.right, y: bounds.top}
+ ], node);
+ // srsName attribute is optional for gml:Box
+ if(this.srsName) {
+ node.setAttribute("srsName", this.srsName);
+ }
+ return node;
+ }
+ }, OpenLayers.Format.GML.Base.prototype.writers["gml"]),
+ "feature": OpenLayers.Format.GML.Base.prototype.writers["feature"],
+ "wfs": OpenLayers.Format.GML.Base.prototype.writers["wfs"]
+ },
+
+ CLASS_NAME: "OpenLayers.Format.GML.v2"
+
+});
+/* ======================================================================
+ 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>
+ */
+OpenLayers.Filter.Function = OpenLayers.Class(OpenLayers.Filter, {
+
+ /**
+ * APIProperty: name
+ * {String} Name of the function.
+ */
+ name: null,
+
+ /**
+ * APIProperty: params
+ * {Array(<OpenLayers.Filter.Function> || 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:
+ * {<OpenLayers.Filter.Function>}
+ */
+
+ 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.XML>
+ */
+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
+ * <OpenLayers.Format.Filter> 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:
+ * {<OpenLayers.Filter>} 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 <PropertyName>attribute</PropertyName>"}
+ 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 {<OpenLayers.Filter.Spatial>} 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:
+ * {<OpenLayers.Filter.Spatial>} 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 <Literal>
+ * 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
+ * (<OpenLayers.Filter.Function> || String || Number)
+ *
+ * Parameters:
+ * value - (<OpenLayers.Filter.Function> || 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 - {<OpenLayers.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<ii; ++i) {
+ this.writeNode("ogc:FeatureId", filter.fids[i], node);
+ }
+ return node;
+ },
+ "FeatureId": function(fid) {
+ return this.createElementNSPlus("ogc:FeatureId", {
+ attributes: {fid: fid}
+ });
+ },
+ "And": function(filter) {
+ var node = this.createElementNSPlus("ogc:And");
+ var childFilter;
+ for (var i=0, ii=filter.filters.length; i<ii; ++i) {
+ childFilter = filter.filters[i];
+ this.writeNode(
+ this.getFilterType(childFilter), childFilter, node
+ );
+ }
+ return node;
+ },
+ "Or": function(filter) {
+ var node = this.createElementNSPlus("ogc:Or");
+ var childFilter;
+ for (var i=0, ii=filter.filters.length; i<ii; ++i) {
+ childFilter = filter.filters[i];
+ this.writeNode(
+ this.getFilterType(childFilter), childFilter, node
+ );
+ }
+ return node;
+ },
+ "Not": function(filter) {
+ var node = this.createElementNSPlus("ogc:Not");
+ var childFilter = filter.filters[0];
+ this.writeNode(
+ this.getFilterType(childFilter), childFilter, node
+ );
+ return node;
+ },
+ "PropertyIsLessThan": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsLessThan");
+ // 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;
+ },
+ "PropertyIsGreaterThan": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsGreaterThan");
+ // 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;
+ },
+ "PropertyIsLessThanOrEqualTo": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsLessThanOrEqualTo");
+ // 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;
+ },
+ "PropertyIsGreaterThanOrEqualTo": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsGreaterThanOrEqualTo");
+ // 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;
+ },
+ "PropertyIsBetween": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsBetween");
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ this.writeNode("LowerBoundary", filter, node);
+ this.writeNode("UpperBoundary", filter, node);
+ return node;
+ },
+ "PropertyName": function(filter) {
+ // no ogc:expression handling for now
+ return this.createElementNSPlus("ogc:PropertyName", {
+ value: filter.property
+ });
+ },
+ "Literal": function(value) {
+ var encode = this.encodeLiteral ||
+ OpenLayers.Format.Filter.v1.prototype.encodeLiteral;
+ return this.createElementNSPlus("ogc:Literal", {
+ value: encode(value)
+ });
+ },
+ "LowerBoundary": function(filter) {
+ // handle Literals or Functions for now
+ var node = this.createElementNSPlus("ogc:LowerBoundary");
+ this.writeOgcExpression(filter.lowerBoundary, node);
+ return node;
+ },
+ "UpperBoundary": function(filter) {
+ // handle Literals or Functions for now
+ var node = this.createElementNSPlus("ogc:UpperBoundary");
+ this.writeNode("Literal", filter.upperBoundary, node);
+ return node;
+ },
+ "INTERSECTS": function(filter) {
+ return this.writeSpatial(filter, "Intersects");
+ },
+ "WITHIN": function(filter) {
+ return this.writeSpatial(filter, "Within");
+ },
+ "CONTAINS": function(filter) {
+ return this.writeSpatial(filter, "Contains");
+ },
+ "DWITHIN": function(filter) {
+ var node = this.writeSpatial(filter, "DWithin");
+ this.writeNode("Distance", filter, node);
+ return node;
+ },
+ "Distance": function(filter) {
+ return this.createElementNSPlus("ogc:Distance", {
+ attributes: {
+ units: filter.distanceUnits
+ },
+ value: filter.distance
+ });
+ },
+ "Function": function(filter) {
+ var node = this.createElementNSPlus("ogc:Function", {
+ attributes: {
+ name: filter.name
+ }
+ });
+ var params = filter.params;
+ for(var i=0, len=params.length; i<len; i++){
+ this.writeOgcExpression(params[i], node);
+ }
+ return node;
+ },
+ "PropertyIsNull": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsNull");
+ this.writeNode("PropertyName", filter, node);
+ return node;
+ }
+ }
+ },
+
+ /**
+ * Method: getFilterType
+ */
+ getFilterType: function(filter) {
+ var filterType = this.filterMap[filter.type];
+ if(!filterType) {
+ throw "Filter writing not supported for rule type: " + filter.type;
+ }
+ return filterType;
+ },
+
+ /**
+ * Property: filterMap
+ * {Object} Contains a member for each filter type. Values are node names
+ * for corresponding OGC Filter child elements.
+ */
+ filterMap: {
+ "&&": "And",
+ "||": "Or",
+ "!": "Not",
+ "==": "PropertyIsEqualTo",
+ "!=": "PropertyIsNotEqualTo",
+ "<": "PropertyIsLessThan",
+ ">": "PropertyIsGreaterThan",
+ "<=": "PropertyIsLessThanOrEqualTo",
+ ">=": "PropertyIsGreaterThanOrEqualTo",
+ "..": "PropertyIsBetween",
+ "~": "PropertyIsLike",
+ "NULL": "PropertyIsNull",
+ "BBOX": "BBOX",
+ "DWITHIN": "DWITHIN",
+ "WITHIN": "WITHIN",
+ "CONTAINS": "CONTAINS",
+ "INTERSECTS": "INTERSECTS",
+ "FID": "_featureIds"
+ },
+
+ CLASS_NAME: "OpenLayers.Format.Filter.v1"
+
+});
+/* ======================================================================
+ OpenLayers/Format/Filter/v1_0_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/GML/v2.js
+ * @requires OpenLayers/Format/Filter/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Filter.v1_0_0
+ * Write ogc:Filter version 1.0.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.GML.v2>
+ * - <OpenLayers.Format.Filter.v1>
+ */
+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
+ * <OpenLayers.Format.Filter> 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 {<OpenLayers.Filter.Spatial>} filter and converts it into XML.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter.Spatial>} 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
+ * <OpenLayers.Format.WFST.v1_0_0> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.Filter.v1_0_0>
+ * - <OpenLayers.Format.WFST.v1>
+ */
+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<len; i++) {
+ this.writeNode(
+ "ogc:PropertyName",
+ {property: options.propertyNames[i]},
+ node
+ );
+ }
+ }
+ if(options.filter) {
+ this.setFilterProperty(options.filter);
+ this.writeNode("ogc:Filter", options.filter, node);
+ }
+ return node;
+ }
+ }, OpenLayers.Format.WFST.v1.prototype.writers["wfs"]),
+ "gml": OpenLayers.Format.GML.v2.prototype.writers["gml"],
+ "feature": OpenLayers.Format.GML.v2.prototype.writers["feature"],
+ "ogc": OpenLayers.Format.Filter.v1_0_0.prototype.writers["ogc"]
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WFST.v1_0_0"
+});
+/* ======================================================================
+ OpenLayers/Renderer/Elements.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.ElementsIndexer
+ * This class takes care of figuring out which order elements should be
+ * placed in the DOM based on given indexing methods.
+ */
+OpenLayers.ElementsIndexer = OpenLayers.Class({
+
+ /**
+ * Property: maxZIndex
+ * {Integer} This is the largest-most z-index value for a node
+ * contained within the indexer.
+ */
+ maxZIndex: null,
+
+ /**
+ * Property: order
+ * {Array<String>} 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 - {<OpenLayers.ElementsIndexer>}
+ * 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 - {<OpenLayers.ElementsIndexer>}
+ * 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 - {<OpenLayers.ElementsIndexer>}
+ * 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>
+ */
+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 <setExtent> method compares this value (which is the one
+ * from the previous <setExtent> 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
+ * {<OpenLayers.ElementIndexer>} 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 - {<OpenLayers.Bounds>}
+ * 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 - {<OpenLayers.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 - {<OpenLayers.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<len; i++) {
+ rendered = this.drawGeometry(
+ geometry.components[i], style, featureId) && rendered;
+ }
+ return rendered;
+ }
+
+ rendered = false;
+ var removeBackground = false;
+ if (style.display != "none") {
+ if (style.backgroundGraphic) {
+ this.redrawBackgroundNode(geometry.id, geometry, style,
+ featureId);
+ } else {
+ removeBackground = true;
+ }
+ rendered = this.redrawNode(geometry.id, geometry, style,
+ featureId);
+ }
+ if (rendered == false) {
+ var node = document.getElementById(geometry.id);
+ if (node) {
+ if (node._style.backgroundGraphic) {
+ removeBackground = true;
+ }
+ node.parentNode.removeChild(node);
+ }
+ }
+ if (removeBackground) {
+ var node = document.getElementById(
+ geometry.id + this.BACKGROUND_ID_SUFFIX);
+ if (node) {
+ node.parentNode.removeChild(node);
+ }
+ }
+ return rendered;
+ },
+
+ /**
+ * Method: redrawNode
+ *
+ * Parameters:
+ * id - {String}
+ * geometry - {<OpenLayers.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
+ */
+ 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 - {<OpenLayers.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 - {<OpenLayers.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 - {<OpenLayers.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 - {<OpenLayers.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 - {<OpenLayers.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 - {<OpenLayers.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 - {<OpenLayers.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 - {<OpenLayers.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 <OpenLayers.Event> 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 - {<OpenLayers.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<len; i++) {
+ this.eraseGeometry(geometry.components[i], featureId);
+ }
+ } else {
+ var element = OpenLayers.Util.getElement(geometry.id);
+ if (element && element.parentNode) {
+ if (element.geometry) {
+ element.geometry.destroy();
+ element.geometry = null;
+ }
+ element.parentNode.removeChild(element);
+
+ if (this.indexer) {
+ this.indexer.remove(element);
+ }
+
+ if (element._style.backgroundGraphic) {
+ var backgroundId = geometry.id + this.BACKGROUND_ID_SUFFIX;
+ var bElem = OpenLayers.Util.getElement(backgroundId);
+ if (bElem && bElem.parentNode) {
+ // No need to destroy the geometry since the element and the background
+ // node share the same geometry.
+ bElem.parentNode.removeChild(bElem);
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: nodeFactory
+ * Create new node of the specified type, with the (optional) specified id.
+ *
+ * If node already exists with same ID and a different type, we remove it
+ * and then call ourselves again to recreate it.
+ *
+ * Parameters:
+ * id - {String}
+ * type - {String} type Kind of node to draw.
+ *
+ * Returns:
+ * {DOMElement} A new node of the given type and id.
+ */
+ nodeFactory: function(id, type) {
+ var node = OpenLayers.Util.getElement(id);
+ if (node) {
+ if (!this.nodeTypeCompare(node, type)) {
+ node.parentNode.removeChild(node);
+ node = this.nodeFactory(id, type);
+ }
+ } else {
+ node = this.createNode(type, id);
+ }
+ return node;
+ },
+
+ /**
+ * Method: nodeTypeCompare
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * type - {String} Kind of node
+ *
+ * Returns:
+ * {Boolean} Whether or not the specified node is of the specified type
+ * This function must be overridden by subclasses.
+ */
+ nodeTypeCompare: function(node, type) {},
+
+ /**
+ * Method: createNode
+ *
+ * Parameters:
+ * type - {String} Kind of node to draw.
+ * id - {String} Id for node.
+ *
+ * Returns:
+ * {DOMElement} A new node of the given type and id.
+ * This function must be overridden by subclasses.
+ */
+ createNode: function(type, id) {},
+
+ /**
+ * Method: moveRoot
+ * moves this renderer's root to a different renderer.
+ *
+ * Parameters:
+ * renderer - {<OpenLayers.Renderer>} target renderer for the moved root
+ */
+ moveRoot: function(renderer) {
+ var root = this.root;
+ if(renderer.root.parentNode == this.rendererRoot) {
+ root = renderer.root;
+ }
+ root.parentNode.removeChild(root);
+ renderer.rendererRoot.appendChild(root);
+ },
+
+ /**
+ * Method: getRenderLayerId
+ * Gets the layer that this renderer's output appears on. If moveRoot was
+ * used, this will be different from the id of the layer containing the
+ * features rendered by this renderer.
+ *
+ * Returns:
+ * {String} the id of the output layer.
+ */
+ getRenderLayerId: function() {
+ return this.root.parentNode.parentNode.id;
+ },
+
+ /**
+ * Method: isComplexSymbol
+ * Determines if a symbol cannot be rendered using drawCircle
+ *
+ * Parameters:
+ * graphicName - {String}
+ *
+ * Returns
+ * {Boolean} true if the symbol is complex, false if not
+ */
+ isComplexSymbol: function(graphicName) {
+ return (graphicName != "circle") && !!graphicName;
+ },
+
+ CLASS_NAME: "OpenLayers.Renderer.Elements"
+});
+
+/* ======================================================================
+ OpenLayers/Control/Panel.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Events/buttonclick.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Panel
+ * The Panel control is a container for other controls. With it toolbars
+ * may be composed.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Panel = OpenLayers.Class(OpenLayers.Control, {
+ /**
+ * Property: controls
+ * {Array(<OpenLayers.Control>)}
+ */
+ controls: null,
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * APIProperty: defaultControl
+ * {<OpenLayers.Control>} The control which is activated when the control is
+ * activated (turned on), which also happens at instantiation.
+ * If <saveState> is true, <defaultControl> 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 <OpenLayers.Control.TYPE_TOOL> 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 <activateControl> method is called.
+ *
+ * Specific properties for controls on a panel:
+ * type - {Number} One of <OpenLayers.Control.TYPE_TOOL>,
+ * <OpenLayers.Control.TYPE_TOGGLE>, <OpenLayers.Control.TYPE_BUTTON>.
+ * If not provided, <OpenLayers.Control.TYPE_TOOL> is assumed.
+ * title - {string} Text displayed when mouse is over the icon that
+ * represents the control.
+ *
+ * The <OpenLayers.Control.type> of a control determines the behavior when
+ * clicking its icon:
+ * <OpenLayers.Control.TYPE_TOOL> - The control is activated and other
+ * controls of this type in the same panel are deactivated. This is
+ * the default type.
+ * <OpenLayers.Control.TYPE_TOGGLE> - The active state of the control is
+ * toggled.
+ * <OpenLayers.Control.TYPE_BUTTON> - The
+ * <OpenLayers.Control.Button.trigger> method of the control is called,
+ * but its active state is not changed.
+ *
+ * If a control is <OpenLayers.Control.active>, 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<len; i++) {
+ control = this.controls[i];
+ if (control === this.defaultControl ||
+ (this.saveState && this.activeState[control.id])) {
+ control.activate();
+ }
+ }
+ if (this.saveState === true) {
+ this.defaultControl = null;
+ }
+ this.redraw();
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * APIMethod: deactivate
+ */
+ deactivate: function() {
+ if (OpenLayers.Control.prototype.deactivate.apply(this, arguments)) {
+ var control;
+ for (var i=0, len=this.controls.length; i<len; i++) {
+ control = this.controls[i];
+ this.activeState[control.id] = control.deactivate();
+ }
+ this.redraw();
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: draw
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+ if (this.outsideViewport) {
+ this.events.attachToElement(this.div);
+ this.events.register("buttonclick", this, this.onButtonClick);
+ } else {
+ this.map.events.register("buttonclick", this, this.onButtonClick);
+ }
+ this.addControlsToMap(this.controls);
+ return this.div;
+ },
+
+ /**
+ * Method: redraw
+ */
+ redraw: function() {
+ for (var l=this.div.childNodes.length, i=l-1; 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<len; i++) {
+ this.div.appendChild(this.controls[i].panel_div);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: activateControl
+ * This method is called when the user click on the icon representing a
+ * control in the panel.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>}
+ */
+ 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<len; i++) {
+ c = this.controls[i];
+ if (c != control &&
+ (c.type === OpenLayers.Control.TYPE_TOOL || c.type == null)) {
+ c.deactivate();
+ }
+ }
+ control.activate();
+ }
+ },
+
+ /**
+ * APIMethod: addControls
+ * To build a toolbar, you add a set of controls to it. addControls
+ * lets you add a single control or a list of controls to the
+ * Control Panel.
+ *
+ * Parameters:
+ * controls - {<OpenLayers.Control>} 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<len; i++) {
+ var control = controls[i],
+ element = this.createControlMarkup(control);
+ OpenLayers.Element.addClass(element,
+ control.displayClass + "ItemInactive");
+ OpenLayers.Element.addClass(element, "olButton");
+ if (control.title != "" && !element.title) {
+ element.title = control.title;
+ }
+ control.panel_div = element;
+ }
+
+ if (this.map) { // map.addControl() has already been called on the panel
+ this.addControlsToMap(controls);
+ this.redraw();
+ }
+ },
+
+ /**
+ * APIMethod: createControlMarkup
+ * This function just creates a div for the control. If specific HTML
+ * markup is needed this function can be overridden in specific classes,
+ * or at panel instantiation time:
+ *
+ * Example:
+ * (code)
+ * var panel = new OpenLayers.Control.Panel({
+ * defaultControl: control,
+ * // ovverride createControlMarkup to create actual buttons
+ * // including texts wrapped into span elements.
+ * createControlMarkup: function(control) {
+ * var button = document.createElement('button'),
+ * span = document.createElement('span');
+ * if (control.text) {
+ * span.innerHTML = control.text;
+ * }
+ * return button;
+ * }
+ * });
+ * (end)
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} 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(<OpenLayers.Control>)} Controls to add into map.
+ */
+ addControlsToMap: function (controls) {
+ var control;
+ for (var i=0, len=controls.length; i<len; i++) {
+ control = controls[i];
+ if (control.autoActivate === true) {
+ control.autoActivate = false;
+ this.map.addControl(control);
+ control.autoActivate = true;
+ } else {
+ this.map.addControl(control);
+ control.deactivate();
+ }
+ control.events.on({
+ activate: this.iconOn,
+ deactivate: this.iconOff
+ });
+ }
+ },
+
+ /**
+ * Method: iconOn
+ * Internal use, for use only with "controls[i].events.on/un".
+ */
+ iconOn: function() {
+ var d = this.panel_div; // "this" refers to a control on panel!
+ var re = new RegExp("\\b(" + this.displayClass + "Item)Inactive\\b");
+ d.className = d.className.replace(re, "$1Active");
+ },
+
+ /**
+ * Method: iconOff
+ * Internal use, for use only with "controls[i].events.on/un".
+ */
+ iconOff: function() {
+ var d = this.panel_div; // "this" refers to a control on panel!
+ var re = new RegExp("\\b(" + this.displayClass + "Item)Active\\b");
+ d.className = d.className.replace(re, "$1Inactive");
+ },
+
+ /**
+ * Method: onButtonClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onButtonClick: function (evt) {
+ var controls = this.controls,
+ button = evt.buttonElement;
+ for (var i=controls.length-1; 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(<OpenLayers.Control>)} 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(<OpenLayers.Control>)} 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(<OpenLayers.Control>)} A list of controls matching the given type.
+ * An empty array is returned if no matches are found.
+ */
+ getControlsByClass: function(match) {
+ return this.getControlsBy("CLASS_NAME", match);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Panel"
+});
+
+/* ======================================================================
+ OpenLayers/Strategy.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy
+ * Abstract vector layer strategy class. Not to be instantiated directly. Use
+ * one of the strategy subclasses instead.
+ */
+OpenLayers.Strategy = OpenLayers.Class({
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>} 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 <layer> property.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>}
+ */
+ setLayer: function(layer) {
+ this.layer = layer;
+ },
+
+ /**
+ * Method: activate
+ * Activate the strategy. Register any listeners, do appropriate setup.
+ *
+ * Returns:
+ * {Boolean} True if the strategy was successfully activated or false if
+ * the strategy was already active.
+ */
+ activate: function() {
+ if (!this.active) {
+ this.active = true;
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivate the strategy. Unregister any listeners, do appropriate
+ * tear-down.
+ *
+ * Returns:
+ * {Boolean} True if the strategy was successfully deactivated or false if
+ * the strategy was already inactive.
+ */
+ deactivate: function() {
+ if (this.active) {
+ this.active = false;
+ return true;
+ }
+ return false;
+ },
+
+ CLASS_NAME: "OpenLayers.Strategy"
+});
+/* ======================================================================
+ OpenLayers/Strategy/Fixed.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Strategy.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy.Fixed
+ * A simple strategy that requests features once and never requests new data.
+ *
+ * Inherits from:
+ * - <OpenLayers.Strategy>
+ */
+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 <activate>.
+ *
+ * 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 - {<OpenLayers.Protocol.Response>} 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<len; ++i) {
+ geom = features[i].geometry;
+ if(geom) {
+ geom.transform(remote, local);
+ }
+ }
+ }
+ layer.addFeatures(features);
+ }
+ layer.events.triggerEvent("loadend", {response: resp});
+ },
+
+ CLASS_NAME: "OpenLayers.Strategy.Fixed"
+});
+/* ======================================================================
+ OpenLayers/Control/Zoom.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.Zoom
+ * The Zoom control is a pair of +/- links for zooming in and out.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Zoom = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: zoomInText
+ * {String}
+ * Text for zoom-in link. Default is "+".
+ */
+ zoomInText: "+",
+
+ /**
+ * APIProperty: zoomInId
+ * {String}
+ * Instead of having the control create a zoom in link, you can provide
+ * the identifier for an anchor element already added to the document.
+ * By default, an element with id "olZoomInLink" will be searched for
+ * and used if it exists.
+ */
+ zoomInId: "olZoomInLink",
+
+ /**
+ * APIProperty: zoomOutText
+ * {String}
+ * Text for zoom-out link. Default is "\u2212".
+ */
+ zoomOutText: "\u2212",
+
+ /**
+ * APIProperty: zoomOutId
+ * {String}
+ * Instead of having the control create a zoom out link, you can provide
+ * the identifier for an anchor element already added to the document.
+ * By default, an element with id "olZoomOutLink" will be searched for
+ * and used if it exists.
+ */
+ zoomOutId: "olZoomOutLink",
+
+ /**
+ * Method: draw
+ *
+ * Returns:
+ * {DOMElement} A reference to the DOMElement containing the zoom links.
+ */
+ draw: function() {
+ var div = OpenLayers.Control.prototype.draw.apply(this),
+ links = this.getOrCreateLinks(div),
+ zoomIn = links.zoomIn,
+ zoomOut = links.zoomOut,
+ eventsInstance = this.map.events;
+
+ if (zoomOut.parentNode !== div) {
+ eventsInstance = this.events;
+ eventsInstance.attachToElement(zoomOut.parentNode);
+ }
+ eventsInstance.register("buttonclick", this, this.onZoomClick);
+
+ this.zoomInLink = zoomIn;
+ this.zoomOutLink = zoomOut;
+ return div;
+ },
+
+ /**
+ * Method: getOrCreateLinks
+ *
+ * Parameters:
+ * el - {DOMElement}
+ *
+ * Return:
+ * {Object} Object with zoomIn and zoomOut properties referencing links.
+ */
+ getOrCreateLinks: function(el) {
+ var zoomIn = document.getElementById(this.zoomInId),
+ zoomOut = document.getElementById(this.zoomOutId);
+ if (!zoomIn) {
+ zoomIn = document.createElement("a");
+ zoomIn.href = "#zoomIn";
+ zoomIn.appendChild(document.createTextNode(this.zoomInText));
+ zoomIn.className = "olControlZoomIn";
+ el.appendChild(zoomIn);
+ }
+ OpenLayers.Element.addClass(zoomIn, "olButton");
+ if (!zoomOut) {
+ zoomOut = document.createElement("a");
+ zoomOut.href = "#zoomOut";
+ zoomOut.appendChild(document.createTextNode(this.zoomOutText));
+ zoomOut.className = "olControlZoomOut";
+ el.appendChild(zoomOut);
+ }
+ OpenLayers.Element.addClass(zoomOut, "olButton");
+ return {
+ zoomIn: zoomIn, zoomOut: zoomOut
+ };
+ },
+
+ /**
+ * Method: onZoomClick
+ * Called when zoomin/out link is clicked.
+ */
+ onZoomClick: function(evt) {
+ var button = evt.buttonElement;
+ if (button === this.zoomInLink) {
+ this.map.zoomIn();
+ } else if (button === this.zoomOutLink) {
+ this.map.zoomOut();
+ }
+ },
+
+ /**
+ * Method: destroy
+ * Clean up.
+ */
+ destroy: function() {
+ if (this.map) {
+ this.map.events.unregister("buttonclick", this, this.onZoomClick);
+ }
+ delete this.zoomInLink;
+ delete this.zoomOutLink;
+ OpenLayers.Control.prototype.destroy.apply(this);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Zoom"
+});
+/* ======================================================================
+ OpenLayers/Protocol.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol
+ * Abstract vector layer protocol class. Not to be instantiated directly. Use
+ * one of the protocol subclasses instead.
+ */
+OpenLayers.Protocol = OpenLayers.Class({
+
+ /**
+ * Property: format
+ * {<OpenLayers.Format>} The format used by this protocol.
+ */
+ format: null,
+
+ /**
+ * Property: options
+ * {Object} Any options sent to the constructor.
+ */
+ options: null,
+
+ /**
+ * Property: autoDestroy
+ * {Boolean} The creator of the protocol can set autoDestroy to false
+ * to fully control when the protocol is destroyed. Defaults to
+ * true.
+ */
+ autoDestroy: true,
+
+ /**
+ * Property: defaultFilter
+ * {<OpenLayers.Filter>} 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 - {<OpenLayers.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:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * 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({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * 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({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * 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 - {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * 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({<OpenLayers.Feature.Vector>})}
+ * 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({<OpenLayers.Protocol.Response>})} An array of
+ * <OpenLayers.Protocol.Response> objects.
+ */
+ commit: function() {
+ },
+
+ /**
+ * Method: abort
+ * Abort an ongoing request.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.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 - {<OpenLayers.Protocol.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({<OpenLayers.Feature.Vector>})} or {<OpenLayers.Feature.Vector>}
+ * 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({<OpenLayers.Feature.Vector>})} or {<OpenLayers.Feature.Vector>}
+ * The features provided by the user and placed in the request by the
+ * protocol.
+ */
+ reqFeatures: null,
+
+ /**
+ * Property: priv
+ */
+ priv: null,
+
+ /**
+ * Property: error
+ * {Object} The error object in case a service exception was encountered.
+ */
+ error: null,
+
+ /**
+ * Constructor: OpenLayers.Protocol.Response
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ },
+
+ /**
+ * Method: success
+ *
+ * Returns:
+ * {Boolean} - true on success, false otherwise
+ */
+ success: function() {
+ return this.code > 0;
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol.Response"
+});
+
+OpenLayers.Protocol.Response.SUCCESS = 1;
+OpenLayers.Protocol.Response.FAILURE = 0;
+/* ======================================================================
+ OpenLayers/Protocol/WFS.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol.WFS
+ * Used to create a versioned WFS protocol. Default version is 1.0.0.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol>} 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 <url>, <featureType>,
+ * <featurePrefix> and <srsName> for WFS <version> 1.1.0. Note that
+ * srsName matching with the WMS layer will not work with WFS 1.0.0.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.WMS>} 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>}
+ */
+OpenLayers.Protocol.WFS.fromWMSLayer = function(layer, options) {
+ var typeName, featurePrefix;
+ var param = layer.params["LAYERS"];
+ var parts = (OpenLayers.Util.isArray(param) ? param[0] : param).split(":");
+ if(parts.length > 1) {
+ featurePrefix = parts[0];
+ }
+ typeName = parts.pop();
+ var protocolOptions = {
+ url: layer.url,
+ featureType: typeName,
+ featurePrefix: featurePrefix,
+ srsName: layer.projection && layer.projection.getCode() ||
+ layer.map && layer.map.getProjectionObject().getCode(),
+ version: "1.1.0"
+ };
+ return new OpenLayers.Protocol.WFS(OpenLayers.Util.applyDefaults(
+ options, protocolOptions
+ ));
+};
+
+/**
+ * Constant: OpenLayers.Protocol.WFS.DEFAULTS
+ */
+OpenLayers.Protocol.WFS.DEFAULTS = {
+ "version": "1.0.0"
+};
+/* ======================================================================
+ OpenLayers/Request.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Events.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+/**
+ * TODO: deprecate me
+ * Use OpenLayers.Request.proxy instead.
+ */
+OpenLayers.ProxyHost = "";
+
+/**
+ * Namespace: OpenLayers.Request
+ * The OpenLayers.Request namespace contains convenience methods for working
+ * with XMLHttpRequests. These methods work with a cross-browser
+ * W3C compliant <OpenLayers.Request.XMLHttpRequest> 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
+ * {<OpenLayers.Events>} An events object that handles all
+ * events on the {<OpenLayers.Request>} object.
+ *
+ * All event listeners will receive an event object with three properties:
+ * request - {<OpenLayers.Request.XMLHttpRequest>} 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 <GET>, <POST>, <PUT>, <DELETE>, <OPTIONS>, or <HEAD>.
+ * 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
+ * <OpenLayers.ProxyHost>.
+ * 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 <GET>
+ * 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 <OpenLayers.Util.getParameterString>.
+ * 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 <POST> and <PUT> requests.
+ * Make sure to provide the appropriate "Content-Type" header for your
+ * data. For <POST> and <PUT> 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 <issue> method, with the method property set
+ * to GET.
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> 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 <issue> 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 <issue> 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 <issue> 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 <issue> 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 <issue> method, with the method property set
+ * to DELETE.
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> 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 <issue> method, with the method property set
+ * to HEAD.
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> 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 <issue> method, with the method property set
+ * to OPTIONS.
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ OPTIONS: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "OPTIONS"});
+ return OpenLayers.Request.issue(config);
+ }
+
+});
+/* ======================================================================
+ OpenLayers/Request/XMLHttpRequest.js
+ ====================================================================== */
+
+// XMLHttpRequest.js Copyright (C) 2010 Sergey Ilinsky (http://www.ilinsky.com)
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @requires OpenLayers/Request.js
+ */
+
+(function () {
+
+ // Save reference to earlier defined object implementation (if any)
+ var oXMLHttpRequest = window.XMLHttpRequest;
+
+ // Define on browser type
+ var bGecko = !!window.controllers,
+ bIE = window.document.all && !window.opera,
+ bIE7 = bIE && window.navigator.userAgent.match(/MSIE 7.0/);
+
+ // Enables "XMLHttpRequest()" call next to "new XMLHttpReques()"
+ function fXMLHttpRequest() {
+ this._object = oXMLHttpRequest && !bIE7 ? new oXMLHttpRequest : new window.ActiveXObject("Microsoft.XMLHTTP");
+ this._listeners = [];
+ };
+
+ // Constructor
+ function cXMLHttpRequest() {
+ return new fXMLHttpRequest;
+ };
+ cXMLHttpRequest.prototype = fXMLHttpRequest.prototype;
+
+ // BUGFIX: Firefox with Firebug installed would break pages if not executed
+ if (bGecko && oXMLHttpRequest.wrapped)
+ cXMLHttpRequest.wrapped = oXMLHttpRequest.wrapped;
+
+ // Constants
+ cXMLHttpRequest.UNSENT = 0;
+ cXMLHttpRequest.OPENED = 1;
+ cXMLHttpRequest.HEADERS_RECEIVED = 2;
+ cXMLHttpRequest.LOADING = 3;
+ cXMLHttpRequest.DONE = 4;
+
+ // Public Properties
+ cXMLHttpRequest.prototype.readyState = cXMLHttpRequest.UNSENT;
+ cXMLHttpRequest.prototype.responseText = '';
+ cXMLHttpRequest.prototype.responseXML = null;
+ cXMLHttpRequest.prototype.status = 0;
+ cXMLHttpRequest.prototype.statusText = '';
+
+ // Priority proposal
+ cXMLHttpRequest.prototype.priority = "NORMAL";
+
+ // Instance-level Events Handlers
+ cXMLHttpRequest.prototype.onreadystatechange = null;
+
+ // Class-level Events Handlers
+ cXMLHttpRequest.onreadystatechange = null;
+ cXMLHttpRequest.onopen = null;
+ cXMLHttpRequest.onsend = null;
+ cXMLHttpRequest.onabort = null;
+
+ // Public Methods
+ cXMLHttpRequest.prototype.open = function(sMethod, sUrl, bAsync, sUser, sPassword) {
+ // Delete headers, required when object is reused
+ delete this._headers;
+
+ // When bAsync parameter value is omitted, use true as default
+ if (arguments.length < 3)
+ bAsync = true;
+
+ // Save async parameter for fixing Gecko bug with missing readystatechange in synchronous requests
+ this._async = bAsync;
+
+ // Set the onreadystatechange handler
+ var oRequest = this,
+ nState = this.readyState,
+ fOnUnload;
+
+ // BUGFIX: IE - memory leak on page unload (inter-page leak)
+ if (bIE && bAsync) {
+ fOnUnload = function() {
+ if (nState != cXMLHttpRequest.DONE) {
+ fCleanTransport(oRequest);
+ // Safe to abort here since onreadystatechange handler removed
+ oRequest.abort();
+ }
+ };
+ window.attachEvent("onunload", fOnUnload);
+ }
+
+ // Add method sniffer
+ if (cXMLHttpRequest.onopen)
+ cXMLHttpRequest.onopen.apply(this, arguments);
+
+ if (arguments.length > 4)
+ this._object.open(sMethod, sUrl, bAsync, sUser, sPassword);
+ else
+ if (arguments.length > 3)
+ this._object.open(sMethod, sUrl, bAsync, sUser);
+ else
+ this._object.open(sMethod, sUrl, bAsync);
+
+ this.readyState = cXMLHttpRequest.OPENED;
+ fReadyStateChange(this);
+
+ this._object.onreadystatechange = function() {
+ if (bGecko && !bAsync)
+ return;
+
+ // Synchronize state
+ oRequest.readyState = oRequest._object.readyState;
+
+ //
+ fSynchronizeValues(oRequest);
+
+ // BUGFIX: Firefox fires unnecessary DONE when aborting
+ if (oRequest._aborted) {
+ // Reset readyState to UNSENT
+ oRequest.readyState = cXMLHttpRequest.UNSENT;
+
+ // Return now
+ return;
+ }
+
+ if (oRequest.readyState == cXMLHttpRequest.DONE) {
+ // Free up queue
+ delete oRequest._data;
+/* if (bAsync)
+ fQueue_remove(oRequest);*/
+ //
+ fCleanTransport(oRequest);
+// Uncomment this block if you need a fix for IE cache
+/*
+ // BUGFIX: IE - cache issue
+ if (!oRequest._object.getResponseHeader("Date")) {
+ // Save object to cache
+ oRequest._cached = oRequest._object;
+
+ // Instantiate a new transport object
+ cXMLHttpRequest.call(oRequest);
+
+ // Re-send request
+ if (sUser) {
+ if (sPassword)
+ oRequest._object.open(sMethod, sUrl, bAsync, sUser, sPassword);
+ else
+ oRequest._object.open(sMethod, sUrl, bAsync, sUser);
+ }
+ else
+ oRequest._object.open(sMethod, sUrl, bAsync);
+ oRequest._object.setRequestHeader("If-Modified-Since", oRequest._cached.getResponseHeader("Last-Modified") || new window.Date(0));
+ // Copy headers set
+ if (oRequest._headers)
+ for (var sHeader in oRequest._headers)
+ if (typeof oRequest._headers[sHeader] == "string") // Some frameworks prototype objects with functions
+ oRequest._object.setRequestHeader(sHeader, oRequest._headers[sHeader]);
+
+ oRequest._object.onreadystatechange = function() {
+ // Synchronize state
+ oRequest.readyState = oRequest._object.readyState;
+
+ if (oRequest._aborted) {
+ //
+ oRequest.readyState = cXMLHttpRequest.UNSENT;
+
+ // Return
+ return;
+ }
+
+ if (oRequest.readyState == cXMLHttpRequest.DONE) {
+ // Clean Object
+ fCleanTransport(oRequest);
+
+ // get cached request
+ if (oRequest.status == 304)
+ oRequest._object = oRequest._cached;
+
+ //
+ delete oRequest._cached;
+
+ //
+ fSynchronizeValues(oRequest);
+
+ //
+ fReadyStateChange(oRequest);
+
+ // BUGFIX: IE - memory leak in interrupted
+ if (bIE && bAsync)
+ window.detachEvent("onunload", fOnUnload);
+ }
+ };
+ oRequest._object.send(null);
+
+ // Return now - wait until re-sent request is finished
+ return;
+ };
+*/
+ // BUGFIX: IE - memory leak in interrupted
+ if (bIE && bAsync)
+ window.detachEvent("onunload", fOnUnload);
+ }
+
+ // BUGFIX: Some browsers (Internet Explorer, Gecko) fire OPEN readystate twice
+ if (nState != oRequest.readyState)
+ fReadyStateChange(oRequest);
+
+ nState = oRequest.readyState;
+ }
+ };
+ function fXMLHttpRequest_send(oRequest) {
+ oRequest._object.send(oRequest._data);
+
+ // BUGFIX: Gecko - missing readystatechange calls in synchronous requests
+ if (bGecko && !oRequest._async) {
+ oRequest.readyState = cXMLHttpRequest.OPENED;
+
+ // Synchronize state
+ fSynchronizeValues(oRequest);
+
+ // Simulate missing states
+ while (oRequest.readyState < cXMLHttpRequest.DONE) {
+ oRequest.readyState++;
+ fReadyStateChange(oRequest);
+ // Check if we are aborted
+ if (oRequest._aborted)
+ return;
+ }
+ }
+ };
+ cXMLHttpRequest.prototype.send = function(vData) {
+ // Add method sniffer
+ if (cXMLHttpRequest.onsend)
+ cXMLHttpRequest.onsend.apply(this, arguments);
+
+ if (!arguments.length)
+ vData = null;
+
+ // BUGFIX: Safari - fails sending documents created/modified dynamically, so an explicit serialization required
+ // BUGFIX: IE - rewrites any custom mime-type to "text/xml" in case an XMLNode is sent
+ // BUGFIX: Gecko - fails sending Element (this is up to the implementation either to standard)
+ if (vData && vData.nodeType) {
+ vData = window.XMLSerializer ? new window.XMLSerializer().serializeToString(vData) : vData.xml;
+ if (!this._headers["Content-Type"])
+ this._object.setRequestHeader("Content-Type", "application/xml");
+ }
+
+ this._data = vData;
+/*
+ // Add to queue
+ if (this._async)
+ fQueue_add(this);
+ else*/
+ fXMLHttpRequest_send(this);
+ };
+ cXMLHttpRequest.prototype.abort = function() {
+ // Add method sniffer
+ if (cXMLHttpRequest.onabort)
+ cXMLHttpRequest.onabort.apply(this, arguments);
+
+ // BUGFIX: Gecko - unnecessary DONE when aborting
+ if (this.readyState > cXMLHttpRequest.UNSENT)
+ this._aborted = true;
+
+ this._object.abort();
+
+ // BUGFIX: IE - memory leak
+ fCleanTransport(this);
+
+ this.readyState = cXMLHttpRequest.UNSENT;
+
+ delete this._data;
+/* if (this._async)
+ fQueue_remove(this);*/
+ };
+ cXMLHttpRequest.prototype.getAllResponseHeaders = function() {
+ return this._object.getAllResponseHeaders();
+ };
+ cXMLHttpRequest.prototype.getResponseHeader = function(sName) {
+ return this._object.getResponseHeader(sName);
+ };
+ cXMLHttpRequest.prototype.setRequestHeader = function(sName, sValue) {
+ // BUGFIX: IE - cache issue
+ if (!this._headers)
+ this._headers = {};
+ this._headers[sName] = sValue;
+
+ return this._object.setRequestHeader(sName, sValue);
+ };
+
+ // EventTarget interface implementation
+ cXMLHttpRequest.prototype.addEventListener = function(sName, fHandler, bUseCapture) {
+ for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++)
+ if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture)
+ return;
+ // Add listener
+ this._listeners.push([sName, fHandler, bUseCapture]);
+ };
+
+ cXMLHttpRequest.prototype.removeEventListener = function(sName, fHandler, bUseCapture) {
+ for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++)
+ if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture)
+ break;
+ // Remove listener
+ if (oListener)
+ this._listeners.splice(nIndex, 1);
+ };
+
+ cXMLHttpRequest.prototype.dispatchEvent = function(oEvent) {
+ var oEventPseudo = {
+ 'type': oEvent.type,
+ 'target': this,
+ 'currentTarget':this,
+ 'eventPhase': 2,
+ 'bubbles': oEvent.bubbles,
+ 'cancelable': oEvent.cancelable,
+ 'timeStamp': oEvent.timeStamp,
+ 'stopPropagation': function() {}, // There is no flow
+ 'preventDefault': function() {}, // There is no default action
+ 'initEvent': function() {} // Original event object should be initialized
+ };
+
+ // Execute onreadystatechange
+ if (oEventPseudo.type == "readystatechange" && this.onreadystatechange)
+ (this.onreadystatechange.handleEvent || this.onreadystatechange).apply(this, [oEventPseudo]);
+
+ // Execute listeners
+ for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++)
+ if (oListener[0] == oEventPseudo.type && !oListener[2])
+ (oListener[1].handleEvent || oListener[1]).apply(this, [oEventPseudo]);
+ };
+
+ //
+ cXMLHttpRequest.prototype.toString = function() {
+ return '[' + "object" + ' ' + "XMLHttpRequest" + ']';
+ };
+
+ cXMLHttpRequest.toString = function() {
+ return '[' + "XMLHttpRequest" + ']';
+ };
+
+ // Helper function
+ function fReadyStateChange(oRequest) {
+ // Sniffing code
+ if (cXMLHttpRequest.onreadystatechange)
+ cXMLHttpRequest.onreadystatechange.apply(oRequest);
+
+ // Fake event
+ oRequest.dispatchEvent({
+ 'type': "readystatechange",
+ 'bubbles': false,
+ 'cancelable': false,
+ 'timeStamp': new Date + 0
+ });
+ };
+
+ function fGetDocument(oRequest) {
+ var oDocument = oRequest.responseXML,
+ sResponse = oRequest.responseText;
+ // Try parsing responseText
+ if (bIE && sResponse && oDocument && !oDocument.documentElement && oRequest.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/)) {
+ oDocument = new window.ActiveXObject("Microsoft.XMLDOM");
+ oDocument.async = false;
+ oDocument.validateOnParse = false;
+ oDocument.loadXML(sResponse);
+ }
+ // Check if there is no error in document
+ if (oDocument)
+ if ((bIE && oDocument.parseError != 0) || !oDocument.documentElement || (oDocument.documentElement && oDocument.documentElement.tagName == "parsererror"))
+ return null;
+ return oDocument;
+ };
+
+ function fSynchronizeValues(oRequest) {
+ try { oRequest.responseText = oRequest._object.responseText; } catch (e) {}
+ try { oRequest.responseXML = fGetDocument(oRequest._object); } catch (e) {}
+ try { oRequest.status = oRequest._object.status; } catch (e) {}
+ try { oRequest.statusText = oRequest._object.statusText; } catch (e) {}
+ };
+
+ function fCleanTransport(oRequest) {
+ // BUGFIX: IE - memory leak (on-page leak)
+ oRequest._object.onreadystatechange = new window.Function;
+ };
+/*
+ // Queue manager
+ var oQueuePending = {"CRITICAL":[],"HIGH":[],"NORMAL":[],"LOW":[],"LOWEST":[]},
+ aQueueRunning = [];
+ function fQueue_add(oRequest) {
+ oQueuePending[oRequest.priority in oQueuePending ? oRequest.priority : "NORMAL"].push(oRequest);
+ //
+ setTimeout(fQueue_process);
+ };
+
+ function fQueue_remove(oRequest) {
+ for (var nIndex = 0, bFound = false; nIndex < aQueueRunning.length; nIndex++)
+ if (bFound)
+ aQueueRunning[nIndex - 1] = aQueueRunning[nIndex];
+ else
+ if (aQueueRunning[nIndex] == oRequest)
+ bFound = true;
+ if (bFound)
+ aQueueRunning.length--;
+ //
+ setTimeout(fQueue_process);
+ };
+
+ function fQueue_process() {
+ if (aQueueRunning.length < 6) {
+ for (var sPriority in oQueuePending) {
+ if (oQueuePending[sPriority].length) {
+ var oRequest = oQueuePending[sPriority][0];
+ oQueuePending[sPriority] = oQueuePending[sPriority].slice(1);
+ //
+ aQueueRunning.push(oRequest);
+ // Send request
+ fXMLHttpRequest_send(oRequest);
+ break;
+ }
+ }
+ }
+ };
+*/
+ // Internet Explorer 5.0 (missing apply)
+ if (!window.Function.prototype.apply) {
+ window.Function.prototype.apply = function(oRequest, oArguments) {
+ if (!oArguments)
+ oArguments = [];
+ oRequest.__func = this;
+ oRequest.__func(oArguments[0], oArguments[1], oArguments[2], oArguments[3], oArguments[4]);
+ delete oRequest.__func;
+ };
+ };
+
+ // Register new object with window
+ /**
+ * Class: OpenLayers.Request.XMLHttpRequest
+ * Standard-compliant (W3C) cross-browser implementation of the
+ * XMLHttpRequest object. From
+ * http://code.google.com/p/xmlhttprequest/.
+ */
+ if (!OpenLayers.Request) {
+ /**
+ * This allows for OpenLayers/Request.js to be included
+ * before or after this script.
+ */
+ OpenLayers.Request = {};
+ }
+ OpenLayers.Request.XMLHttpRequest = cXMLHttpRequest;
+})();
+/* ======================================================================
+ OpenLayers/Format/KML.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Date.js
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ * @requires OpenLayers/Geometry/Collection.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Format.KML
+ * Read/Write KML. Create a new instance with the <OpenLayers.Format.KML>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+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 <extractTracks> 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>.
+ */
+ 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(<OpenLayers.Feature.Vector>)} 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(<OpenLayers.Feature.Vector>)} 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<len; ++i) {
+ var type = types[i];
+
+ var nodes = this.getElementsByTagNameNS(data, "*", type);
+
+ // skip to next type if no nodes are found
+ if(nodes.length == 0) {
+ continue;
+ }
+
+ switch (type.toLowerCase()) {
+
+ // Fetch external links
+ case "link":
+ case "networklink":
+ this.parseLinks(nodes, options);
+ break;
+
+ // parse style information
+ case "style":
+ if (this.extractStyles) {
+ this.parseStyles(nodes, options);
+ }
+ break;
+ case "stylemap":
+ if (this.extractStyles) {
+ this.parseStyleMaps(nodes, options);
+ }
+ break;
+
+ // parse features
+ case "placemark":
+ this.parseFeatures(nodes, options);
+ break;
+ }
+ }
+
+ return this.features;
+ },
+
+ /**
+ * Method: parseLinks
+ * Finds URLs of linked KML documents and fetches them
+ *
+ * Parameters:
+ * nodes - {Array} of {DOMElement} data to read/parse.
+ * options - {Object} Hash of options
+ *
+ */
+ parseLinks: function(nodes, options) {
+
+ // Fetch external links <NetworkLink> and <Link>
+ // 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<len; i++) {
+ var href = this.parseProperty(nodes[i], "*", "href");
+ if(href && !this.fetched[href]) {
+ this.fetched[href] = true; // prevent reloading the same urls
+ var data = this.fetchLink(href);
+ if (data) {
+ this.parseData(data, newOptions);
+ }
+ }
+ }
+
+ },
+
+ /**
+ * Method: fetchLink
+ * Fetches a URL and returns the result
+ *
+ * Parameters:
+ * href - {String} url to be fetched
+ *
+ */
+ fetchLink: function(href) {
+ var request = OpenLayers.Request.GET({url: href, async: false});
+ if (request) {
+ return request.responseText;
+ }
+ },
+
+ /**
+ * Method: parseStyles
+ * Parses <Style> 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<len; i++) {
+ var style = this.parseStyle(nodes[i]);
+ if(style) {
+ var styleName = (options.styleBaseUrl || "") + "#" + style.id;
+
+ this.styles[styleName] = style;
+ }
+ }
+ },
+
+ /**
+ * Method: parseKmlColor
+ * Parses a kml color (in 'aabbggrr' format) and returns the corresponding
+ * color and opacity or null if the color is invalid.
+ *
+ * Parameters:
+ * kmlColor - {String} a kml formated color
+ *
+ * Returns:
+ * {Object}
+ */
+ parseKmlColor: function(kmlColor) {
+ var color = null;
+ if (kmlColor) {
+ var matches = kmlColor.match(this.regExes.kmlColor);
+ if (matches) {
+ color = {
+ color: '#' + matches[4] + matches[3] + matches[2],
+ opacity: parseInt(matches[1], 16) / 255
+ };
+ }
+ }
+ return color;
+ },
+
+ /**
+ * Method: parseStyle
+ * Parses the children of a <Style> node and builds the style hash
+ * accordingly
+ *
+ * Parameters:
+ * node - {DOMElement} <Style> node
+ *
+ */
+ parseStyle: function(node) {
+ var style = {};
+
+ var types = ["LineStyle", "PolyStyle", "IconStyle", "BalloonStyle",
+ "LabelStyle"];
+ var type, styleTypeNode, nodeList, geometry, parser;
+ for(var i=0, len=types.length; i<len; ++i) {
+ type = types[i];
+ styleTypeNode = this.getElementsByTagNameNS(node, "*", type)[0];
+ if(!styleTypeNode) {
+ continue;
+ }
+
+ // only deal with first geometry of this type
+ switch (type.toLowerCase()) {
+ case "linestyle":
+ var kmlColor = this.parseProperty(styleTypeNode, "*", "color");
+ var color = this.parseKmlColor(kmlColor);
+ if (color) {
+ style["strokeColor"] = color.color;
+ style["strokeOpacity"] = color.opacity;
+ }
+
+ var width = this.parseProperty(styleTypeNode, "*", "width");
+ if (width) {
+ style["strokeWidth"] = width;
+ }
+ break;
+
+ case "polystyle":
+ var kmlColor = this.parseProperty(styleTypeNode, "*", "color");
+ var color = this.parseKmlColor(kmlColor);
+ if (color) {
+ style["fillOpacity"] = color.opacity;
+ style["fillColor"] = color.color;
+ }
+ // Check if fill is disabled
+ var fill = this.parseProperty(styleTypeNode, "*", "fill");
+ if (fill == "0") {
+ style["fillColor"] = "none";
+ }
+ // Check if outline is disabled
+ var outline = this.parseProperty(styleTypeNode, "*", "outline");
+ if (outline == "0") {
+ style["strokeWidth"] = "0";
+ }
+
+ break;
+
+ case "iconstyle":
+ // set scale
+ var scale = parseFloat(this.parseProperty(styleTypeNode,
+ "*", "scale") || 1);
+
+ // set default width and height of icon
+ var width = 32 * scale;
+ var height = 32 * scale;
+
+ var iconNode = this.getElementsByTagNameNS(styleTypeNode,
+ "*",
+ "Icon")[0];
+ if (iconNode) {
+ var href = this.parseProperty(iconNode, "*", "href");
+ if (href) {
+
+ var w = this.parseProperty(iconNode, "*", "w");
+ var h = this.parseProperty(iconNode, "*", "h");
+
+ // Settings for Google specific icons that are 64x64
+ // We set the width and height to 64 and halve the
+ // scale to prevent icons from being too big
+ var google = "http://maps.google.com/mapfiles/kml";
+ if (OpenLayers.String.startsWith(
+ href, google) && !w && !h) {
+ w = 64;
+ h = 64;
+ scale = scale / 2;
+ }
+
+ // if only dimension is defined, make sure the
+ // other one has the same value
+ w = w || h;
+ h = h || w;
+
+ if (w) {
+ width = parseInt(w) * scale;
+ }
+
+ if (h) {
+ height = parseInt(h) * scale;
+ }
+
+ // support for internal icons
+ // (/root://icons/palette-x.png)
+ // x and y tell the position on the palette:
+ // - in pixels
+ // - starting from the left bottom
+ // We translate that to a position in the list
+ // and request the appropriate icon from the
+ // google maps website
+ var matches = href.match(this.regExes.kmlIconPalette);
+ if (matches) {
+ var palette = matches[1];
+ var file_extension = matches[2];
+
+ var x = this.parseProperty(iconNode, "*", "x");
+ var y = this.parseProperty(iconNode, "*", "y");
+
+ var posX = x ? x/32 : 0;
+ var posY = y ? (7 - y/32) : 7;
+
+ var pos = posY * 8 + posX;
+ href = "http://maps.google.com/mapfiles/kml/pal"
+ + palette + "/icon" + pos + file_extension;
+ }
+
+ style["graphicOpacity"] = 1; // fully opaque
+ style["externalGraphic"] = href;
+ }
+
+ }
+
+
+ // hotSpots define the offset for an Icon
+ var hotSpotNode = this.getElementsByTagNameNS(styleTypeNode,
+ "*",
+ "hotSpot")[0];
+ if (hotSpotNode) {
+ var x = parseFloat(hotSpotNode.getAttribute("x"));
+ var y = parseFloat(hotSpotNode.getAttribute("y"));
+
+ var xUnits = hotSpotNode.getAttribute("xunits");
+ if (xUnits == "pixels") {
+ style["graphicXOffset"] = -x * scale;
+ }
+ else if (xUnits == "insetPixels") {
+ style["graphicXOffset"] = -width + (x * scale);
+ }
+ else if (xUnits == "fraction") {
+ style["graphicXOffset"] = -width * x;
+ }
+
+ var yUnits = hotSpotNode.getAttribute("yunits");
+ if (yUnits == "pixels") {
+ style["graphicYOffset"] = -height + (y * scale) + 1;
+ }
+ else if (yUnits == "insetPixels") {
+ style["graphicYOffset"] = -(y * scale) + 1;
+ }
+ else if (yUnits == "fraction") {
+ style["graphicYOffset"] = -height * (1 - y) + 1;
+ }
+ }
+
+ style["graphicWidth"] = width;
+ style["graphicHeight"] = height;
+ break;
+
+ case "balloonstyle":
+ var balloonStyle = OpenLayers.Util.getXmlNodeValue(
+ styleTypeNode);
+ if (balloonStyle) {
+ style["balloonStyle"] = balloonStyle.replace(
+ this.regExes.straightBracket, "${$1}");
+ }
+ break;
+ case "labelstyle":
+ var kmlColor = this.parseProperty(styleTypeNode, "*", "color");
+ var color = this.parseKmlColor(kmlColor);
+ if (color) {
+ style["fontColor"] = color.color;
+ style["fontOpacity"] = color.opacity;
+ }
+ break;
+
+ default:
+ }
+ }
+
+ // Some polygons have no line color, so we use the fillColor for that
+ if (!style["strokeColor"] && style["fillColor"]) {
+ style["strokeColor"] = style["fillColor"];
+ }
+
+ var id = node.getAttribute("id");
+ if (id && style) {
+ style.id = id;
+ }
+
+ return style;
+ },
+
+ /**
+ * Method: parseStyleMaps
+ * Parses <StyleMap> nodes, but only uses the 'normal' key
+ *
+ * Parameters:
+ * nodes - {Array} of {DOMElement} data to read/parse.
+ * options - {Object} Hash of options
+ *
+ */
+ parseStyleMaps: function(nodes, options) {
+ // Only the default or "normal" part of the StyleMap is processed now
+ // To do the select or "highlight" bit, we'd need to change lots more
+
+ for(var i=0, len=nodes.length; i<len; i++) {
+ var node = nodes[i];
+ var pairs = this.getElementsByTagNameNS(node, "*",
+ "Pair");
+
+ var id = node.getAttribute("id");
+ for (var j=0, jlen=pairs.length; j<jlen; j++) {
+ var pair = pairs[j];
+ // Use the shortcut in the SLD format to quickly retrieve the
+ // value of a node. Maybe it's good to have a method in
+ // Format.XML to do this
+ var key = this.parseProperty(pair, "*", "key");
+ var styleUrl = this.parseProperty(pair, "*", "styleUrl");
+
+ if (styleUrl && key == "normal") {
+ this.styles[(options.styleBaseUrl || "") + "#" + id] =
+ this.styles[(options.styleBaseUrl || "") + styleUrl];
+ }
+
+ // TODO: implement the "select" part
+ //if (styleUrl && key == "highlight") {
+ //}
+
+ }
+ }
+
+ },
+
+
+ /**
+ * Method: parseFeatures
+ * Loop through all Placemark nodes and parse them.
+ * Will create a list of features
+ *
+ * Parameters:
+ * nodes - {Array} of {DOMElement} data to read/parse.
+ * options - {Object} Hash of options
+ *
+ */
+ parseFeatures: function(nodes, options) {
+ var features = [];
+ for(var i=0, len=nodes.length; i<len; i++) {
+ var featureNode = nodes[i];
+ var feature = this.parseFeature.apply(this,[featureNode]) ;
+ if(feature) {
+
+ // Create reference to styleUrl
+ if (this.extractStyles && feature.attributes &&
+ feature.attributes.styleUrl) {
+ feature.style = this.getStyle(feature.attributes.styleUrl, options);
+ }
+
+ if (this.extractStyles) {
+ // Make sure that <Style> nodes within a placemark are
+ // processed as well
+ var inlineStyleNode = this.getElementsByTagNameNS(featureNode,
+ "*",
+ "Style")[0];
+ if (inlineStyleNode) {
+ var inlineStyle= this.parseStyle(inlineStyleNode);
+ if (inlineStyle) {
+ feature.style = OpenLayers.Util.extend(
+ feature.style, inlineStyle
+ );
+ }
+ }
+ }
+
+ // check if gx:Track elements should be parsed
+ if (this.extractTracks) {
+ var tracks = this.getElementsByTagNameNS(
+ featureNode, this.namespaces.gx, "Track"
+ );
+ if (tracks && tracks.length > 0) {
+ var track = tracks[0];
+ var container = {
+ features: [],
+ feature: feature
+ };
+ this.readNode(track, container);
+ if (container.features.length > 0) {
+ features.push.apply(features, container.features);
+ }
+ }
+ } else {
+ // add feature to list of features
+ features.push(feature);
+ }
+ } else {
+ throw "Bad Placemark: " + i;
+ }
+ }
+
+ // add new features to existing feature list
+ this.features = this.features.concat(features);
+ },
+
+ /**
+ * 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: {
+ "kml": {
+ "when": function(node, container) {
+ container.whens.push(OpenLayers.Date.parse(
+ this.getChildValue(node)
+ ));
+ },
+ "_trackPointAttribute": function(node, container) {
+ var name = node.nodeName.split(":").pop();
+ container.attributes[name].push(this.getChildValue(node));
+ }
+ },
+ "gx": {
+ "Track": function(node, container) {
+ var obj = {
+ whens: [],
+ points: [],
+ angles: []
+ };
+ if (this.trackAttributes) {
+ var name;
+ obj.attributes = {};
+ for (var i=0, ii=this.trackAttributes.length; i<ii; ++i) {
+ name = this.trackAttributes[i];
+ obj.attributes[name] = [];
+ if (!(name in this.readers.kml)) {
+ this.readers.kml[name] = this.readers.kml._trackPointAttribute;
+ }
+ }
+ }
+ this.readChildNodes(node, obj);
+ if (obj.whens.length !== obj.points.length) {
+ throw new Error("gx:Track with unequal number of when (" +
+ obj.whens.length + ") and gx:coord (" +
+ obj.points.length + ") elements.");
+ }
+ var hasAngles = obj.angles.length > 0;
+ if (hasAngles && obj.whens.length !== obj.angles.length) {
+ throw new Error("gx:Track with unequal number of when (" +
+ obj.whens.length + ") and gx:angles (" +
+ obj.angles.length + ") elements.");
+ }
+ var feature, point, angles;
+ for (var i=0, ii=obj.whens.length; i<ii; ++i) {
+ feature = container.feature.clone();
+ feature.fid = container.feature.fid || container.feature.id;
+ point = obj.points[i];
+ feature.geometry = point;
+ if ("z" in point) {
+ feature.attributes.altitude = point.z;
+ }
+ if (this.internalProjection && this.externalProjection) {
+ feature.geometry.transform(
+ this.externalProjection, this.internalProjection
+ );
+ }
+ if (this.trackAttributes) {
+ for (var j=0, jj=this.trackAttributes.length; j<jj; ++j) {
+ var name = this.trackAttributes[j];
+ feature.attributes[name] = obj.attributes[name][i];
+ }
+ }
+ feature.attributes.when = obj.whens[i];
+ feature.attributes.trackId = container.feature.id;
+ if (hasAngles) {
+ angles = obj.angles[i];
+ feature.attributes.heading = parseFloat(angles[0]);
+ feature.attributes.tilt = parseFloat(angles[1]);
+ feature.attributes.roll = parseFloat(angles[2]);
+ }
+ container.features.push(feature);
+ }
+ },
+ "coord": function(node, container) {
+ var str = this.getChildValue(node);
+ var coords = str.replace(this.regExes.trimSpace, "").split(/\s+/);
+ var point = new OpenLayers.Geometry.Point(coords[0], coords[1]);
+ if (coords.length > 2) {
+ point.z = parseFloat(coords[2]);
+ }
+ container.points.push(point);
+ },
+ "angles": function(node, container) {
+ var str = this.getChildValue(node);
+ var parts = str.replace(this.regExes.trimSpace, "").split(/\s+/);
+ container.angles.push(parts);
+ }
+ }
+ },
+
+ /**
+ * Method: parseFeature
+ * This function is the core of the KML parsing code in OpenLayers.
+ * It creates the geometries that are then attached to the returned
+ * feature, and calls parseAttributes() to get attribute data out.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A vector feature.
+ */
+ parseFeature: function(node) {
+ // only accept one geometry per feature - look for highest "order"
+ var order = ["MultiGeometry", "Polygon", "LineString", "Point"];
+ var type, nodeList, geometry, parser;
+ for(var i=0, len=order.length; i<len; ++i) {
+ type = order[i];
+ this.internalns = node.namespaceURI ?
+ node.namespaceURI : this.kmlns;
+ nodeList = this.getElementsByTagNameNS(node,
+ this.internalns, type);
+ if(nodeList.length > 0) {
+ // only deal with first geometry of this type
+ var 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;
+ }
+ }
+
+ // construct feature (optionally with attributes)
+ var attributes;
+ if(this.extractAttributes) {
+ attributes = this.parseAttributes(node);
+ }
+ var feature = new OpenLayers.Feature.Vector(geometry, attributes);
+
+ var fid = node.getAttribute("id") || node.getAttribute("name");
+ if(fid != null) {
+ feature.fid = fid;
+ }
+
+ return feature;
+ },
+
+ /**
+ * Method: getStyle
+ * Retrieves a style from a style hash using styleUrl as the key
+ * If the styleUrl doesn't exist yet, we try to fetch it
+ * Internet
+ *
+ * Parameters:
+ * styleUrl - {String} URL of style
+ * options - {Object} Hash of options
+ *
+ * Returns:
+ * {Object} - (reference to) Style hash
+ */
+ getStyle: function(styleUrl, options) {
+
+ var styleBaseUrl = OpenLayers.Util.removeTail(styleUrl);
+
+ var newOptions = OpenLayers.Util.extend({}, options);
+ newOptions.depth++;
+ newOptions.styleBaseUrl = styleBaseUrl;
+
+ // Fetch remote Style URLs (if not fetched before)
+ if (!this.styles[styleUrl]
+ && !OpenLayers.String.startsWith(styleUrl, "#")
+ && newOptions.depth <= this.maxDepth
+ && !this.fetched[styleBaseUrl] ) {
+
+ var data = this.fetchLink(styleBaseUrl);
+ if (data) {
+ this.parseData(data, newOptions);
+ }
+
+ }
+
+ // return requested style
+ var style = OpenLayers.Util.extend({}, this.styles[styleUrl]);
+ return style;
+ },
+
+ /**
+ * Property: parseGeometry
+ * Properties of this object are the functions that parse geometries based
+ * on their type.
+ */
+ parseGeometry: {
+
+ /**
+ * Method: parseGeometry.point
+ * Given a KML node representing a point geometry, create an OpenLayers
+ * point geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A KML Point node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} A point geometry.
+ */
+ point: function(node) {
+ var nodeList = this.getElementsByTagNameNS(node, this.internalns,
+ "coordinates");
+ var coords = [];
+ if(nodeList.length > 0) {
+ var coordString = nodeList[0].firstChild.nodeValue;
+ coordString = coordString.replace(this.regExes.removeSpace, "");
+ coords = coordString.split(",");
+ }
+
+ var point = null;
+ if(coords.length > 1) {
+ // preserve third dimension
+ if(coords.length == 2) {
+ coords[2] = null;
+ }
+ point = new OpenLayers.Geometry.Point(coords[0], coords[1],
+ coords[2]);
+ } else {
+ throw "Bad coordinate string: " + coordString;
+ }
+ return point;
+ },
+
+ /**
+ * Method: parseGeometry.linestring
+ * Given a KML node representing a linestring geometry, create an
+ * OpenLayers linestring geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A KML LineString node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.LineString>} A linestring geometry.
+ */
+ linestring: function(node, ring) {
+ var nodeList = this.getElementsByTagNameNS(node, this.internalns,
+ "coordinates");
+ var line = null;
+ if(nodeList.length > 0) {
+ var coordString = this.getChildValue(nodeList[0]);
+
+ coordString = coordString.replace(this.regExes.trimSpace,
+ "");
+ coordString = coordString.replace(this.regExes.trimComma,
+ ",");
+ var pointList = coordString.split(this.regExes.splitSpace);
+ var numPoints = pointList.length;
+ var points = new Array(numPoints);
+ var coords, numCoords;
+ for(var i=0; i<numPoints; ++i) {
+ coords = pointList[i].split(",");
+ numCoords = coords.length;
+ if(numCoords > 1) {
+ if(coords.length == 2) {
+ coords[2] = null;
+ }
+ points[i] = new OpenLayers.Geometry.Point(coords[0],
+ coords[1],
+ coords[2]);
+ } else {
+ throw "Bad LineString point coordinates: " +
+ pointList[i];
+ }
+ }
+ if(numPoints) {
+ if(ring) {
+ line = new OpenLayers.Geometry.LinearRing(points);
+ } else {
+ line = new OpenLayers.Geometry.LineString(points);
+ }
+ } else {
+ throw "Bad LineString coordinates: " + coordString;
+ }
+ }
+
+ return line;
+ },
+
+ /**
+ * Method: parseGeometry.polygon
+ * Given a KML node representing a polygon geometry, create an
+ * OpenLayers polygon geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A KML Polygon node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Polygon>} A polygon geometry.
+ */
+ polygon: function(node) {
+ var nodeList = this.getElementsByTagNameNS(node, this.internalns,
+ "LinearRing");
+ var numRings = nodeList.length;
+ var components = new Array(numRings);
+ if(numRings > 0) {
+ // this assumes exterior ring first, inner rings after
+ var ring;
+ for(var i=0, len=nodeList.length; i<len; ++i) {
+ ring = this.parseGeometry.linestring.apply(this,
+ [nodeList[i], true]);
+ if(ring) {
+ components[i] = ring;
+ } else {
+ throw "Bad LinearRing geometry: " + i;
+ }
+ }
+ }
+ return new OpenLayers.Geometry.Polygon(components);
+ },
+
+ /**
+ * Method: parseGeometry.multigeometry
+ * Given a KML node representing a multigeometry, create an
+ * OpenLayers geometry collection.
+ *
+ * Parameters:
+ * node - {DOMElement} A KML MultiGeometry node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Collection>} A geometry collection.
+ */
+ multigeometry: function(node) {
+ var child, parser;
+ var parts = [];
+ var children = node.childNodes;
+ for(var i=0, len=children.length; i<len; ++i ) {
+ child = children[i];
+ if(child.nodeType == 1) {
+ var type = (child.prefix) ?
+ child.nodeName.split(":")[1] :
+ child.nodeName;
+ var parser = this.parseGeometry[type.toLowerCase()];
+ if(parser) {
+ parts.push(parser.apply(this, [child]));
+ }
+ }
+ }
+ return new OpenLayers.Geometry.Collection(parts);
+ }
+
+ },
+
+ /**
+ * Method: parseAttributes
+ *
+ * Parameters:
+ * node - {DOMElement}
+ *
+ * Returns:
+ * {Object} An attributes object.
+ */
+ parseAttributes: function(node) {
+ var attributes = {};
+
+ // Extended Data is parsed first.
+ var edNodes = node.getElementsByTagName("ExtendedData");
+ if (edNodes.length) {
+ attributes = this.parseExtendedData(edNodes[0]);
+ }
+
+ // assume attribute nodes are type 1 children with a type 3 or 4 child
+ var child, grandchildren, grandchild;
+ var children = node.childNodes;
+
+ for(var i=0, len=children.length; i<len; ++i) {
+ child = children[i];
+ if(child.nodeType == 1) {
+ grandchildren = child.childNodes;
+ if(grandchildren.length >= 1 && grandchildren.length <= 3) {
+ var grandchild;
+ switch (grandchildren.length) {
+ case 1:
+ grandchild = grandchildren[0];
+ break;
+ case 2:
+ var c1 = grandchildren[0];
+ var c2 = grandchildren[1];
+ grandchild = (c1.nodeType == 3 || c1.nodeType == 4) ?
+ c1 : c2;
+ break;
+ case 3:
+ default:
+ grandchild = grandchildren[1];
+ break;
+ }
+ if(grandchild.nodeType == 3 || grandchild.nodeType == 4) {
+ var name = (child.prefix) ?
+ child.nodeName.split(":")[1] :
+ child.nodeName;
+ var value = OpenLayers.Util.getXmlNodeValue(grandchild);
+ if (value) {
+ value = value.replace(this.regExes.trimSpace, "");
+ attributes[name] = value;
+ }
+ }
+ }
+ }
+ }
+ return attributes;
+ },
+
+ /**
+ * Method: parseExtendedData
+ * Parse ExtendedData from KML. Limited support for schemas/datatypes.
+ * See http://code.google.com/apis/kml/documentation/kmlreference.html#extendeddata
+ * for more information on extendeddata.
+ */
+ parseExtendedData: function(node) {
+ var attributes = {};
+ var i, len, data, key;
+ var dataNodes = node.getElementsByTagName("Data");
+ for (i = 0, len = dataNodes.length; i < len; i++) {
+ data = dataNodes[i];
+ key = data.getAttribute("name");
+ var ed = {};
+ var valueNode = data.getElementsByTagName("value");
+ if (valueNode.length) {
+ ed['value'] = this.getChildValue(valueNode[0]);
+ }
+ if (this.kvpAttributes) {
+ attributes[key] = ed['value'];
+ } else {
+ var nameNode = data.getElementsByTagName("displayName");
+ if (nameNode.length) {
+ ed['displayName'] = this.getChildValue(nameNode[0]);
+ }
+ attributes[key] = ed;
+ }
+ }
+ var simpleDataNodes = node.getElementsByTagName("SimpleData");
+ for (i = 0, len = simpleDataNodes.length; i < len; i++) {
+ var ed = {};
+ data = simpleDataNodes[i];
+ key = data.getAttribute("name");
+ ed['value'] = this.getChildValue(data);
+ if (this.kvpAttributes) {
+ attributes[key] = ed['value'];
+ } else {
+ ed['displayName'] = key;
+ attributes[key] = ed;
+ }
+ }
+
+ return attributes;
+ },
+
+ /**
+ * Method: parseProperty
+ * Convenience method to find a node and return its value
+ *
+ * Parameters:
+ * xmlNode - {<DOMElement>}
+ * namespace - {String} namespace of the node to find
+ * tagName - {String} name of the property to parse
+ *
+ * Returns:
+ * {String} The value for the requested property (defaults to null)
+ */
+ parseProperty: function(xmlNode, namespace, tagName) {
+ var value;
+ var nodeList = this.getElementsByTagNameNS(xmlNode, namespace, tagName);
+ try {
+ value = OpenLayers.Util.getXmlNodeValue(nodeList[0]);
+ } catch(e) {
+ value = null;
+ }
+
+ return value;
+ },
+
+ /**
+ * APIMethod: write
+ * Accept Feature Collection, and return a string.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} An array of features.
+ *
+ * Returns:
+ * {String} A KML string.
+ */
+ write: function(features) {
+ if(!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+ var kml = this.createElementNS(this.kmlns, "kml");
+ var folder = this.createFolderXML();
+ for(var i=0, len=features.length; i<len; ++i) {
+ folder.appendChild(this.createPlacemarkXML(features[i]));
+ }
+ kml.appendChild(folder);
+ return OpenLayers.Format.XML.prototype.write.apply(this, [kml]);
+ },
+
+ /**
+ * Method: createFolderXML
+ * Creates and returns a KML folder node
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ createFolderXML: function() {
+ // Folder
+ var folder = this.createElementNS(this.kmlns, "Folder");
+
+ // Folder name
+ if (this.foldersName) {
+ var folderName = this.createElementNS(this.kmlns, "name");
+ var folderNameText = this.createTextNode(this.foldersName);
+ folderName.appendChild(folderNameText);
+ folder.appendChild(folderName);
+ }
+
+ // Folder description
+ if (this.foldersDesc) {
+ var folderDesc = this.createElementNS(this.kmlns, "description");
+ var folderDescText = this.createTextNode(this.foldersDesc);
+ folderDesc.appendChild(folderDescText);
+ folder.appendChild(folderDesc);
+ }
+
+ return folder;
+ },
+
+ /**
+ * Method: createPlacemarkXML
+ * Creates and returns a KML placemark node representing the given feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ createPlacemarkXML: function(feature) {
+ // Placemark name
+ var placemarkName = this.createElementNS(this.kmlns, "name");
+ var label = (feature.style && feature.style.label) ? feature.style.label : feature.id;
+ var name = feature.attributes.name || label;
+ placemarkName.appendChild(this.createTextNode(name));
+
+ // Placemark description
+ var placemarkDesc = this.createElementNS(this.kmlns, "description");
+ var desc = feature.attributes.description || this.placemarksDesc;
+ placemarkDesc.appendChild(this.createTextNode(desc));
+
+ // Placemark
+ var placemarkNode = this.createElementNS(this.kmlns, "Placemark");
+ if(feature.fid != null) {
+ placemarkNode.setAttribute("id", feature.fid);
+ }
+ placemarkNode.appendChild(placemarkName);
+ placemarkNode.appendChild(placemarkDesc);
+
+ // Geometry node (Point, LineString, etc. nodes)
+ var geometryNode = this.buildGeometryNode(feature.geometry);
+ placemarkNode.appendChild(geometryNode);
+
+ // output attributes as extendedData
+ if (feature.attributes) {
+ var edNode = this.buildExtendedData(feature.attributes);
+ if (edNode) {
+ placemarkNode.appendChild(edNode);
+ }
+ }
+
+ return placemarkNode;
+ },
+
+ /**
+ * Method: buildGeometryNode
+ * Builds and returns a KML geometry node with the given geometry.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ buildGeometryNode: function(geometry) {
+ var className = geometry.CLASS_NAME;
+ var type = className.substring(className.lastIndexOf(".") + 1);
+ var builder = this.buildGeometry[type.toLowerCase()];
+ var node = null;
+ if(builder) {
+ node = builder.apply(this, [geometry]);
+ }
+ return node;
+ },
+
+ /**
+ * Property: buildGeometry
+ * Object containing methods to do the actual geometry node building
+ * based on geometry type.
+ */
+ buildGeometry: {
+ // TBD: Anybody care about namespace aliases here (these nodes have
+ // no prefixes)?
+
+ /**
+ * Method: buildGeometry.point
+ * Given an OpenLayers point geometry, create a KML point.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Point>} A point geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML point node.
+ */
+ point: function(geometry) {
+ var kml = this.createElementNS(this.kmlns, "Point");
+ kml.appendChild(this.buildCoordinatesNode(geometry));
+ return kml;
+ },
+
+ /**
+ * Method: buildGeometry.multipoint
+ * Given an OpenLayers multipoint geometry, create a KML
+ * GeometryCollection.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Point>} A multipoint geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML GeometryCollection node.
+ */
+ multipoint: function(geometry) {
+ return this.buildGeometry.collection.apply(this, [geometry]);
+ },
+
+ /**
+ * Method: buildGeometry.linestring
+ * Given an OpenLayers linestring geometry, create a KML linestring.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.LineString>} A linestring geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML linestring node.
+ */
+ linestring: function(geometry) {
+ var kml = this.createElementNS(this.kmlns, "LineString");
+ kml.appendChild(this.buildCoordinatesNode(geometry));
+ return kml;
+ },
+
+ /**
+ * Method: buildGeometry.multilinestring
+ * Given an OpenLayers multilinestring geometry, create a KML
+ * GeometryCollection.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Point>} A multilinestring geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML GeometryCollection node.
+ */
+ multilinestring: function(geometry) {
+ return this.buildGeometry.collection.apply(this, [geometry]);
+ },
+
+ /**
+ * Method: buildGeometry.linearring
+ * Given an OpenLayers linearring geometry, create a KML linearring.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.LinearRing>} A linearring geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML linearring node.
+ */
+ linearring: function(geometry) {
+ var kml = this.createElementNS(this.kmlns, "LinearRing");
+ kml.appendChild(this.buildCoordinatesNode(geometry));
+ return kml;
+ },
+
+ /**
+ * Method: buildGeometry.polygon
+ * Given an OpenLayers polygon geometry, create a KML polygon.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Polygon>} A polygon geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML polygon node.
+ */
+ polygon: function(geometry) {
+ var kml = this.createElementNS(this.kmlns, "Polygon");
+ var rings = geometry.components;
+ var ringMember, ringGeom, type;
+ for(var i=0, len=rings.length; i<len; ++i) {
+ type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
+ ringMember = this.createElementNS(this.kmlns, type);
+ ringGeom = this.buildGeometry.linearring.apply(this,
+ [rings[i]]);
+ ringMember.appendChild(ringGeom);
+ kml.appendChild(ringMember);
+ }
+ return kml;
+ },
+
+ /**
+ * Method: buildGeometry.multipolygon
+ * Given an OpenLayers multipolygon geometry, create a KML
+ * GeometryCollection.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Point>} A multipolygon geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML GeometryCollection node.
+ */
+ multipolygon: function(geometry) {
+ return this.buildGeometry.collection.apply(this, [geometry]);
+ },
+
+ /**
+ * Method: buildGeometry.collection
+ * Given an OpenLayers geometry collection, create a KML MultiGeometry.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Collection>} A geometry collection.
+ *
+ * Returns:
+ * {DOMElement} A KML MultiGeometry node.
+ */
+ collection: function(geometry) {
+ var kml = this.createElementNS(this.kmlns, "MultiGeometry");
+ var child;
+ for(var i=0, len=geometry.components.length; i<len; ++i) {
+ child = this.buildGeometryNode.apply(this,
+ [geometry.components[i]]);
+ if(child) {
+ kml.appendChild(child);
+ }
+ }
+ return kml;
+ }
+ },
+
+ /**
+ * Method: buildCoordinatesNode
+ * Builds and returns the KML coordinates node with the given geometry
+ * <coordinates>...</coordinates>
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ buildCoordinatesNode: function(geometry) {
+ var coordinatesNode = this.createElementNS(this.kmlns, "coordinates");
+
+ var path;
+ var points = geometry.components;
+ if(points) {
+ // LineString or LinearRing
+ var point;
+ var numPoints = points.length;
+ var parts = new Array(numPoints);
+ for(var i=0; i<numPoints; ++i) {
+ point = points[i];
+ parts[i] = this.buildCoordinates(point);
+ }
+ path = parts.join(" ");
+ } else {
+ // Point
+ path = this.buildCoordinates(geometry);
+ }
+
+ var txtNode = this.createTextNode(path);
+ coordinatesNode.appendChild(txtNode);
+
+ return coordinatesNode;
+ },
+
+ /**
+ * Method: buildCoordinates
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns
+ * {String} a coordinate pair
+ */
+ buildCoordinates: function(point) {
+ if (this.internalProjection && this.externalProjection) {
+ point = point.clone();
+ point.transform(this.internalProjection,
+ this.externalProjection);
+ }
+ return point.x + "," + point.y;
+ },
+
+ /**
+ * Method: buildExtendedData
+ *
+ * Parameters:
+ * attributes - {Object}
+ *
+ * Returns
+ * {DOMElement} A KML ExtendedData node or {null} if no attributes.
+ */
+ buildExtendedData: function(attributes) {
+ var extendedData = this.createElementNS(this.kmlns, "ExtendedData");
+ for (var attributeName in attributes) {
+ // empty, name, description, styleUrl attributes ignored
+ if (attributes[attributeName] && attributeName != "name" && attributeName != "description" && attributeName != "styleUrl") {
+ var data = this.createElementNS(this.kmlns, "Data");
+ data.setAttribute("name", attributeName);
+ var value = this.createElementNS(this.kmlns, "value");
+ if (typeof attributes[attributeName] == "object") {
+ // cater for object attributes with 'value' properties
+ // other object properties will output an empty node
+ if (attributes[attributeName].value) {
+ value.appendChild(this.createTextNode(attributes[attributeName].value));
+ }
+ if (attributes[attributeName].displayName) {
+ var displayName = this.createElementNS(this.kmlns, "displayName");
+ // displayName always written as CDATA
+ displayName.appendChild(this.getXMLDoc().createCDATASection(attributes[attributeName].displayName));
+ data.appendChild(displayName);
+ }
+ } else {
+ value.appendChild(this.createTextNode(attributes[attributeName]));
+ }
+ data.appendChild(value);
+ extendedData.appendChild(data);
+ }
+ }
+ if (this.isSimpleContent(extendedData)) {
+ return null;
+ } else {
+ return extendedData;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.KML"
+});
+/* ======================================================================
+ OpenLayers/Protocol/WFS/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/Protocol/WFS.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol.WFS.v1
+ * Abstract class for for v1.0.0 and v1.1.0 protocol.
+ *
+ * Inherits from:
+ * - <OpenLayers.Protocol>
+ */
+OpenLayers.Protocol.WFS.v1 = OpenLayers.Class(OpenLayers.Protocol, {
+
+ /**
+ * Property: version
+ * {String} WFS version number.
+ */
+ version: null,
+
+ /**
+ * Property: srsName
+ * {String} Name of spatial reference system. Default is "EPSG:4326".
+ */
+ srsName: "EPSG:4326",
+
+ /**
+ * Property: featureType
+ * {String} Local feature typeName.
+ */
+ featureType: null,
+
+ /**
+ * Property: featureNS
+ * {String} Feature namespace.
+ */
+ featureNS: null,
+
+ /**
+ * Property: geometryName
+ * {String} Name of the geometry attribute for features. Default is
+ * "the_geom" for WFS <version> 1.0, and null for higher versions.
+ */
+ geometryName: "the_geom",
+
+ /**
+ * Property: maxFeatures
+ * {Integer} Optional maximum number of features to retrieve.
+ */
+
+ /**
+ * Property: schema
+ * {String} Optional schema location that will be included in the
+ * schemaLocation attribute value. Note that the feature type schema
+ * is required for a strict XML validator (on transactions with an
+ * insert for example), but is *not* required by the WFS specification
+ * (since the server is supposed to know about feature type schemas).
+ */
+ schema: null,
+
+ /**
+ * Property: featurePrefix
+ * {String} Namespace alias for feature type. Default is "feature".
+ */
+ featurePrefix: "feature",
+
+ /**
+ * Property: formatOptions
+ * {Object} Optional options for the format. If a format is not provided,
+ * this property can be used to extend the default format options.
+ */
+ formatOptions: null,
+
+ /**
+ * Property: readFormat
+ * {<OpenLayers.Format>} For WFS requests it is possible to get a
+ * different output format than GML. In that case, we cannot parse
+ * the response with the default format (WFST) and we need a different
+ * format for reading.
+ */
+ readFormat: null,
+
+ /**
+ * Property: readOptions
+ * {Object} Optional object to pass to format's read.
+ */
+ readOptions: null,
+
+ /**
+ * Constructor: OpenLayers.Protocol.WFS
+ * A class for giving layers WFS protocol.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options properties:
+ * url - {String} URL to send requests to (required).
+ * featureType - {String} Local (without prefix) feature typeName (required).
+ * featureNS - {String} Feature namespace (required, but can be autodetected
+ * during the first query if GML is used as readFormat and
+ * featurePrefix is provided and matches the prefix used by the server
+ * for this featureType).
+ * featurePrefix - {String} Feature namespace alias (optional - only used
+ * for writing if featureNS is provided). Default is 'feature'.
+ * geometryName - {String} Name of geometry attribute. The default is
+ * 'the_geom' for WFS <version> 1.0, and null for higher versions. If
+ * null, it will be set to the name of the first geometry found in the
+ * first read operation.
+ * multi - {Boolean} If set to true, geometries will be casted to Multi
+ * geometries before they are written in a transaction. No casting will
+ * be done when reading features.
+ */
+ initialize: function(options) {
+ OpenLayers.Protocol.prototype.initialize.apply(this, [options]);
+ if(!options.format) {
+ this.format = OpenLayers.Format.WFST(OpenLayers.Util.extend({
+ version: this.version,
+ featureType: this.featureType,
+ featureNS: this.featureNS,
+ featurePrefix: this.featurePrefix,
+ geometryName: this.geometryName,
+ srsName: this.srsName,
+ schema: this.schema
+ }, this.formatOptions));
+ }
+ if (!options.geometryName && parseFloat(this.format.version) > 1.0) {
+ this.setGeometryName(null);
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the protocol.
+ */
+ destroy: function() {
+ if(this.options && !this.options.format) {
+ this.format.destroy();
+ }
+ this.format = null;
+ OpenLayers.Protocol.prototype.destroy.apply(this);
+ },
+
+ /**
+ * APIMethod: read
+ * Construct a request for reading new features. Since WFS splits the
+ * basic CRUD operations into GetFeature requests (for read) and
+ * Transactions (for all others), this method does not make use of the
+ * format's read method (that is only about reading transaction
+ * responses).
+ *
+ * Parameters:
+ * options - {Object} Options for the read operation, in addition to the
+ * options set on the instance (options set here will take precedence).
+ *
+ * To use a configured protocol to get e.g. a WFS hit count, applications
+ * could do the following:
+ *
+ * (code)
+ * protocol.read({
+ * readOptions: {output: "object"},
+ * resultType: "hits",
+ * maxFeatures: null,
+ * callback: function(resp) {
+ * // process resp.numberOfFeatures here
+ * }
+ * });
+ * (end)
+ *
+ * To use a configured protocol to use WFS paging (if supported by the
+ * server), applications could do the following:
+ *
+ * (code)
+ * protocol.read({
+ * startIndex: 0,
+ * count: 50
+ * });
+ * (end)
+ *
+ * To limit the attributes returned by the GetFeature request, applications
+ * can use the propertyNames option to specify the properties to include in
+ * the response:
+ *
+ * (code)
+ * protocol.read({
+ * propertyNames: ["DURATION", "INTENSITY"]
+ * });
+ * (end)
+ */
+ read: function(options) {
+ OpenLayers.Protocol.prototype.read.apply(this, arguments);
+ options = OpenLayers.Util.extend({}, options);
+ OpenLayers.Util.applyDefaults(options, this.options || {});
+ var response = new OpenLayers.Protocol.Response({requestType: "read"});
+
+ var data = OpenLayers.Format.XML.prototype.write.apply(
+ this.format, [this.format.writeNode("wfs:GetFeature", options)]
+ );
+
+ response.priv = OpenLayers.Request.POST({
+ url: options.url,
+ callback: this.createCallback(this.handleRead, response, options),
+ params: options.params,
+ headers: options.headers,
+ data: data
+ });
+
+ return response;
+ },
+
+ /**
+ * APIMethod: setFeatureType
+ * Change the feature type on the fly.
+ *
+ * Parameters:
+ * featureType - {String} Local (without prefix) feature typeName.
+ */
+ setFeatureType: function(featureType) {
+ this.featureType = featureType;
+ this.format.featureType = featureType;
+ },
+
+ /**
+ * APIMethod: setGeometryName
+ * Sets the geometryName option after instantiation.
+ *
+ * Parameters:
+ * geometryName - {String} Name of geometry attribute.
+ */
+ setGeometryName: function(geometryName) {
+ this.geometryName = geometryName;
+ this.format.geometryName = geometryName;
+ },
+
+ /**
+ * Method: handleRead
+ * Deal with response from the read request.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>} The response object to pass
+ * to the user callback.
+ * options - {Object} The user options passed to the read call.
+ */
+ handleRead: function(response, options) {
+ options = OpenLayers.Util.extend({}, options);
+ OpenLayers.Util.applyDefaults(options, this.options);
+
+ if(options.callback) {
+ var request = response.priv;
+ if(request.status >= 200 && request.status < 300) {
+ // success
+ var result = this.parseResponse(request, options.readOptions);
+ if (result && result.success !== false) {
+ if (options.readOptions && options.readOptions.output == "object") {
+ OpenLayers.Util.extend(response, result);
+ } else {
+ response.features = result;
+ }
+ response.code = OpenLayers.Protocol.Response.SUCCESS;
+ } else {
+ // failure (service exception)
+ response.code = OpenLayers.Protocol.Response.FAILURE;
+ response.error = result;
+ }
+ } else {
+ // failure
+ response.code = OpenLayers.Protocol.Response.FAILURE;
+ }
+ options.callback.call(options.scope, response);
+ }
+ },
+
+ /**
+ * Method: parseResponse
+ * Read HTTP response body and return features
+ *
+ * Parameters:
+ * request - {XMLHttpRequest} The request object
+ * options - {Object} Optional object to pass to format's read
+ *
+ * Returns:
+ * {Object} or {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>}
+ * An object with a features property, an array of features or a single
+ * feature.
+ */
+ parseResponse: function(request, options) {
+ var doc = request.responseXML;
+ if(!doc || !doc.documentElement) {
+ doc = request.responseText;
+ }
+ if(!doc || doc.length <= 0) {
+ return null;
+ }
+ var result = (this.readFormat !== null) ? this.readFormat.read(doc) :
+ this.format.read(doc, options);
+ if (!this.featureNS) {
+ var format = this.readFormat || this.format;
+ this.featureNS = format.featureNS;
+ // no need to auto-configure again on subsequent reads
+ format.autoConfig = false;
+ if (!this.geometryName) {
+ this.setGeometryName(format.geometryName);
+ }
+ }
+ return result;
+ },
+
+ /**
+ * Method: commit
+ * Given a list of feature, assemble a batch request for update, create,
+ * and delete transactions. A commit call on the prototype amounts
+ * to writing a WFS transaction - so the write method on the format
+ * is used.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ * options - {Object}
+ *
+ * Valid options properties:
+ * nativeElements - {Array({Object})} Array of objects with information for writing
+ * out <Native> elements, these objects have vendorId, safeToIgnore and
+ * value properties. The <Native> element is intended to allow access to
+ * vendor specific capabilities of any particular web feature server or
+ * datastore.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} A response object with a features
+ * property containing any insertIds and a priv property referencing
+ * the XMLHttpRequest object.
+ */
+ commit: function(features, options) {
+
+ options = OpenLayers.Util.extend({}, options);
+ OpenLayers.Util.applyDefaults(options, this.options);
+
+ var response = new OpenLayers.Protocol.Response({
+ requestType: "commit",
+ reqFeatures: features
+ });
+ response.priv = OpenLayers.Request.POST({
+ url: options.url,
+ headers: options.headers,
+ data: this.format.write(features, options),
+ callback: this.createCallback(this.handleCommit, response, options)
+ });
+
+ return response;
+ },
+
+ /**
+ * Method: handleCommit
+ * Called when the commit request returns.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>} The response object to pass
+ * to the user callback.
+ * options - {Object} The user options passed to the commit call.
+ */
+ handleCommit: function(response, options) {
+ if(options.callback) {
+ var request = response.priv;
+
+ // ensure that we have an xml doc
+ var data = request.responseXML;
+ if(!data || !data.documentElement) {
+ data = request.responseText;
+ }
+
+ var obj = this.format.read(data) || {};
+
+ response.insertIds = obj.insertIds || [];
+ if (obj.success) {
+ response.code = OpenLayers.Protocol.Response.SUCCESS;
+ } else {
+ response.code = OpenLayers.Protocol.Response.FAILURE;
+ response.error = obj;
+ }
+ options.callback.call(options.scope, response);
+ }
+ },
+
+ /**
+ * Method: filterDelete
+ * Send a request that deletes all features by their filter.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>} filter
+ */
+ filterDelete: function(filter, options) {
+ options = OpenLayers.Util.extend({}, options);
+ OpenLayers.Util.applyDefaults(options, this.options);
+
+ var response = new OpenLayers.Protocol.Response({
+ requestType: "commit"
+ });
+
+ var root = this.format.createElementNSPlus("wfs:Transaction", {
+ attributes: {
+ service: "WFS",
+ version: this.version
+ }
+ });
+
+ var deleteNode = this.format.createElementNSPlus("wfs:Delete", {
+ attributes: {
+ typeName: (options.featureNS ? this.featurePrefix + ":" : "") +
+ options.featureType
+ }
+ });
+
+ if(options.featureNS) {
+ deleteNode.setAttribute("xmlns:" + this.featurePrefix, options.featureNS);
+ }
+ var filterNode = this.format.writeNode("ogc:Filter", filter);
+
+ deleteNode.appendChild(filterNode);
+
+ root.appendChild(deleteNode);
+
+ var data = OpenLayers.Format.XML.prototype.write.apply(
+ this.format, [root]
+ );
+
+ return OpenLayers.Request.POST({
+ url: this.url,
+ callback : options.callback || function(){},
+ data: data
+ });
+
+ },
+
+ /**
+ * Method: abort
+ * Abort an ongoing request, the response object passed to
+ * this method must come from this protocol (as a result
+ * of a read, or commit operation).
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>}
+ */
+ abort: function(response) {
+ if (response) {
+ response.priv.abort();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol.WFS.v1"
+});
+/* ======================================================================
+ OpenLayers/Handler/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/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Feature
+ * Handler to respond to mouse events related to a drawn feature. Callbacks
+ * with the following keys will be notified of the following events
+ * associated with features: click, clickout, over, out, and dblclick.
+ *
+ * This handler stops event propagation for mousedown and mouseup if those
+ * browser events target features that can be selected.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Feature = OpenLayers.Class(OpenLayers.Handler, {
+
+ /**
+ * Property: EVENTMAP
+ * {Object} A object mapping the browser events to objects with callback
+ * keys for in and out.
+ */
+ EVENTMAP: {
+ 'click': {'in': 'click', 'out': 'clickout'},
+ 'mousemove': {'in': 'over', 'out': 'out'},
+ 'dblclick': {'in': 'dblclick', 'out': null},
+ 'mousedown': {'in': null, 'out': null},
+ 'mouseup': {'in': null, 'out': null},
+ 'touchstart': {'in': 'click', 'out': 'clickout'}
+ },
+
+ /**
+ * Property: feature
+ * {<OpenLayers.Feature.Vector>} The last feature that was hovered.
+ */
+ feature: null,
+
+ /**
+ * Property: lastFeature
+ * {<OpenLayers.Feature.Vector>} The last feature that was handled.
+ */
+ lastFeature: null,
+
+ /**
+ * Property: down
+ * {<OpenLayers.Pixel>} The location of the last mousedown.
+ */
+ down: null,
+
+ /**
+ * Property: up
+ * {<OpenLayers.Pixel>} The location of the last mouseup.
+ */
+ up: null,
+
+ /**
+ * Property: clickTolerance
+ * {Number} The number of pixels the mouse can move between mousedown
+ * and mouseup for the event to still be considered a click.
+ * Dragging the map should not trigger the click and clickout callbacks
+ * unless the map is moved by less than this tolerance. Defaults to 4.
+ */
+ clickTolerance: 4,
+
+ /**
+ * Property: geometryTypes
+ * To restrict dragging to a limited set of geometry types, send a list
+ * of strings corresponding to the geometry class names.
+ *
+ * @type Array(String)
+ */
+ geometryTypes: null,
+
+ /**
+ * Property: stopClick
+ * {Boolean} If stopClick is set to true, handled clicks do not
+ * propagate to other click listeners. Otherwise, handled clicks
+ * do propagate. Unhandled clicks always propagate, whatever the
+ * value of stopClick. Defaults to true.
+ */
+ stopClick: true,
+
+ /**
+ * Property: stopDown
+ * {Boolean} If stopDown is set to true, handled mousedowns do not
+ * propagate to other mousedown listeners. Otherwise, handled
+ * mousedowns do propagate. Unhandled mousedowns always propagate,
+ * whatever the value of stopDown. Defaults to true.
+ */
+ stopDown: true,
+
+ /**
+ * Property: stopUp
+ * {Boolean} If stopUp is set to true, handled mouseups do not
+ * propagate to other mouseup listeners. Otherwise, handled mouseups
+ * do propagate. Unhandled mouseups always propagate, whatever the
+ * value of stopUp. Defaults to false.
+ */
+ stopUp: false,
+
+ /**
+ * Constructor: OpenLayers.Handler.Feature
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>}
+ * layer - {<OpenLayers.Layer.Vector>}
+ * callbacks - {Object} An object with a 'over' property whos value is
+ * a function to be called when the mouse is over a feature. The
+ * callback should expect to recieve a single argument, the feature.
+ * options - {Object}
+ */
+ initialize: function(control, layer, callbacks, options) {
+ OpenLayers.Handler.prototype.initialize.apply(this, [control, callbacks, options]);
+ this.layer = layer;
+ },
+
+ /**
+ * Method: touchstart
+ * Handle touchstart events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchstart: function(evt) {
+ this.startTouch();
+ return OpenLayers.Event.isMultiTouch(evt) ?
+ true : this.mousedown(evt);
+ },
+
+ /**
+ * Method: touchmove
+ * Handle touchmove events. We just prevent the browser default behavior,
+ * for Android Webkit not to select text when moving the finger after
+ * selecting a feature.
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ touchmove: function(evt) {
+ OpenLayers.Event.preventDefault(evt);
+ },
+
+ /**
+ * Method: mousedown
+ * Handle mouse down. Stop propagation if a feature is targeted by this
+ * event (stops map dragging during feature selection).
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ mousedown: function(evt) {
+ // Feature selection is only done with a left click. Other handlers may stop the
+ // propagation of left-click mousedown events but not right-click mousedown events.
+ // This mismatch causes problems when comparing the location of the down and up
+ // events in the click function so it is important ignore right-clicks.
+ if (OpenLayers.Event.isLeftClick(evt) || OpenLayers.Event.isSingleTouch(evt)) {
+ this.down = evt.xy;
+ }
+ return this.handle(evt) ? !this.stopDown : true;
+ },
+
+ /**
+ * Method: mouseup
+ * Handle mouse up. Stop propagation if a feature is targeted by this
+ * event.
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ mouseup: function(evt) {
+ this.up = evt.xy;
+ return this.handle(evt) ? !this.stopUp : true;
+ },
+
+ /**
+ * Method: click
+ * Handle click. Call the "click" callback if click on a feature,
+ * or the "clickout" callback if click outside any feature.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ click: function(evt) {
+ return this.handle(evt) ? !this.stopClick : true;
+ },
+
+ /**
+ * Method: mousemove
+ * Handle mouse moves. Call the "over" callback if moving in to a feature,
+ * or the "out" callback if moving out of a feature.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ mousemove: function(evt) {
+ if (!this.callbacks['over'] && !this.callbacks['out']) {
+ return true;
+ }
+ this.handle(evt);
+ return true;
+ },
+
+ /**
+ * Method: dblclick
+ * Handle dblclick. Call the "dblclick" callback if dblclick on a feature.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ dblclick: function(evt) {
+ return !this.handle(evt);
+ },
+
+ /**
+ * Method: geometryTypeMatches
+ * Return true if the geometry type of the passed feature matches
+ * one of the geometry types in the geometryTypes array.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Vector.Feature>}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ geometryTypeMatches: function(feature) {
+ return this.geometryTypes == null ||
+ OpenLayers.Util.indexOf(this.geometryTypes,
+ feature.geometry.CLASS_NAME) > -1;
+ },
+
+ /**
+ * Method: handle
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} The event occurred over a relevant feature.
+ */
+ handle: function(evt) {
+ if(this.feature && !this.feature.layer) {
+ // feature has been destroyed
+ this.feature = null;
+ }
+ var type = evt.type;
+ var handled = false;
+ var previouslyIn = !!(this.feature); // previously in a feature
+ var click = (type == "click" || type == "dblclick" || type == "touchstart");
+ this.feature = this.layer.getFeatureFromEvent(evt);
+ if(this.feature && !this.feature.layer) {
+ // feature has been destroyed
+ this.feature = null;
+ }
+ if(this.lastFeature && !this.lastFeature.layer) {
+ // last feature has been destroyed
+ this.lastFeature = null;
+ }
+ if(this.feature) {
+ if(type === "touchstart") {
+ // stop the event to prevent Android Webkit from
+ // "flashing" the map div
+ OpenLayers.Event.preventDefault(evt);
+ }
+ var inNew = (this.feature != this.lastFeature);
+ if(this.geometryTypeMatches(this.feature)) {
+ // in to a feature
+ if(previouslyIn && inNew) {
+ // out of last feature and in to another
+ if(this.lastFeature) {
+ this.triggerCallback(type, 'out', [this.lastFeature]);
+ }
+ this.triggerCallback(type, 'in', [this.feature]);
+ } else if(!previouslyIn || click) {
+ // in feature for the first time
+ this.triggerCallback(type, 'in', [this.feature]);
+ }
+ this.lastFeature = this.feature;
+ handled = true;
+ } else {
+ // not in to a feature
+ if(this.lastFeature && (previouslyIn && inNew || click)) {
+ // out of last feature for the first time
+ this.triggerCallback(type, 'out', [this.lastFeature]);
+ }
+ // next time the mouse goes in a feature whose geometry type
+ // doesn't match we don't want to call the 'out' callback
+ // again, so let's set this.feature to null so that
+ // previouslyIn will evaluate to false the next time
+ // we enter handle. Yes, a bit hackish...
+ this.feature = null;
+ }
+ } else if(this.lastFeature && (previouslyIn || click)) {
+ this.triggerCallback(type, 'out', [this.lastFeature]);
+ }
+ return handled;
+ },
+
+ /**
+ * Method: triggerCallback
+ * Call the callback keyed in the event map with the supplied arguments.
+ * For click and clickout, the <clickTolerance> is checked first.
+ *
+ * Parameters:
+ * type - {String}
+ */
+ triggerCallback: function(type, mode, args) {
+ var key = this.EVENTMAP[type][mode];
+ if(key) {
+ if(type == 'click' && this.up && this.down) {
+ // for click/clickout, only trigger callback if tolerance is met
+ var dpx = Math.sqrt(
+ Math.pow(this.up.x - this.down.x, 2) +
+ Math.pow(this.up.y - this.down.y, 2)
+ );
+ if(dpx <= this.clickTolerance) {
+ this.callback(key, args);
+ }
+ // we're done with this set of events now: clear the cached
+ // positions so we can't trip over them later (this can occur
+ // if one of the up/down events gets eaten before it gets to us
+ // but we still get the click)
+ this.up = this.down = null;
+ } else {
+ this.callback(key, args);
+ }
+ }
+ },
+
+ /**
+ * Method: activate
+ * Turn on the handler. Returns false if the handler was already active.
+ *
+ * Returns:
+ * {Boolean}
+ */
+ activate: function() {
+ var activated = false;
+ if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ this.moveLayerToTop();
+ this.map.events.on({
+ "removelayer": this.handleMapEvents,
+ "changelayer": this.handleMapEvents,
+ scope: this
+ });
+ activated = true;
+ }
+ return activated;
+ },
+
+ /**
+ * Method: deactivate
+ * Turn off the handler. Returns false if the handler was already active.
+ *
+ * Returns:
+ * {Boolean}
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ this.moveLayerBack();
+ this.feature = null;
+ this.lastFeature = null;
+ this.down = null;
+ this.up = null;
+ this.map.events.un({
+ "removelayer": this.handleMapEvents,
+ "changelayer": this.handleMapEvents,
+ scope: this
+ });
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ /**
+ * 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.Handler.Feature"
+});
+/* ======================================================================
+ 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 {<OpenLayers.Style>}, 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 - {<OpenLayers.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
+ * <OpenLayers.Layer.Vector> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer>
+ */
+OpenLayers.Layer.Vector = OpenLayers.Class(OpenLayers.Layer, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.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 <OpenLayers.Layer.events>):
+ * 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(<OpenLayers.Feature.Vector>)}
+ */
+ features: null,
+
+ /**
+ * Property: filter
+ * {<OpenLayers.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(<OpenLayers.Feature.Vector>)}
+ */
+ 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
+ * {<OpenLayers.StyleMap>}
+ */
+ styleMap: null,
+
+ /**
+ * Property: strategies
+ * {Array(<OpenLayers.Strategy>})} Optional list of strategies for the layer.
+ */
+ strategies: null,
+
+ /**
+ * Property: protocol
+ * {<OpenLayers.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
+ * {<OpenLayers.Renderer>}
+ */
+ renderer: null,
+
+ /**
+ * APIProperty: rendererOptions
+ * {Object} Options for the renderer. See {<OpenLayers.Renderer>} 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:
+ * {<OpenLayers.Layer.Vector>} 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<len; i++) {
+ this.strategies[i].setLayer(this);
+ }
+ }
+
+ },
+
+ /**
+ * APIMethod: destroy
+ * Destroy this layer
+ */
+ destroy: function() {
+ if (this.strategies) {
+ var strategy, i, len;
+ for(i=0, len=this.strategies.length; i<len; i++) {
+ strategy = this.strategies[i];
+ if(strategy.autoDestroy) {
+ strategy.destroy();
+ }
+ }
+ this.strategies = null;
+ }
+ if (this.protocol) {
+ if(this.protocol.autoDestroy) {
+ this.protocol.destroy();
+ }
+ this.protocol = null;
+ }
+ this.destroyFeatures();
+ this.features = null;
+ this.selectedFeatures = null;
+ this.unrenderedFeatures = null;
+ if (this.renderer) {
+ this.renderer.destroy();
+ }
+ this.renderer = null;
+ this.geometryType = null;
+ this.drawn = null;
+ OpenLayers.Layer.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer.
+ *
+ * Note: Features of the layer are also cloned.
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Vector>} 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<len; ++i) {
+ clonedFeatures[i] = features[i].clone();
+ }
+ obj.features = clonedFeatures;
+
+ return obj;
+ },
+
+ /**
+ * Method: refresh
+ * Ask the layer to request features again and redraw them. Triggers
+ * the refresh event if the layer is in range and visible.
+ *
+ * Parameters:
+ * obj - {Object} Optional object with properties for any listener of
+ * the refresh event.
+ */
+ refresh: function(obj) {
+ if(this.calculateInRange() && this.visibility) {
+ this.events.triggerEvent("refresh", obj);
+ }
+ },
+
+ /**
+ * Method: assignRenderer
+ * Iterates through the available renderer implementations and selects
+ * and assigns the first one whose "supported()" function returns true.
+ */
+ assignRenderer: function() {
+ for (var i=0, len=this.renderers.length; i<len; i++) {
+ var rendererClass = this.renderers[i];
+ var renderer = (typeof rendererClass == "function") ?
+ rendererClass :
+ OpenLayers.Renderer[rendererClass];
+ if (renderer && renderer.prototype.supported()) {
+ this.renderer = new renderer(this.div, this.rendererOptions);
+ break;
+ }
+ }
+ },
+
+ /**
+ * Method: displayError
+ * Let the user know their browser isn't supported.
+ */
+ displayError: function() {
+ if (this.reportError) {
+ OpenLayers.Console.userError(OpenLayers.i18n("browserNotSupported",
+ {renderers: this. renderers.join('\n')}));
+ }
+ },
+
+ /**
+ * Method: setMap
+ * The layer has been added to the map.
+ *
+ * If there is no renderer set, the layer can't be used. Remove it.
+ * Otherwise, give the renderer a reference to the map and set its size.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ 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<len; i++) {
+ strategy = this.strategies[i];
+ if(strategy.autoActivate) {
+ strategy.activate();
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: removeMap
+ * The layer has been removed from the map.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ this.drawn = false;
+ if(this.strategies) {
+ var strategy, i, len;
+ for(i=0, len=this.strategies.length; i<len; i++) {
+ strategy = this.strategies[i];
+ if(strategy.autoActivate) {
+ strategy.deactivate();
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: onMapResize
+ * Notify the renderer of the change in size.
+ *
+ */
+ onMapResize: function() {
+ OpenLayers.Layer.prototype.onMapResize.apply(this, arguments);
+
+ var newSize = this.map.getSize();
+ newSize.w = newSize.w * this.ratio;
+ newSize.h = newSize.h * this.ratio;
+ this.renderer.setSize(newSize);
+ },
+
+ /**
+ * Method: moveTo
+ * Reset the vector layer's div so that it once again is lined up with
+ * the map. Notify the renderer of the change of extent, and in the
+ * case of a change of zoom level (resolution), have the
+ * renderer redraw features.
+ *
+ * If the layer has not yet been drawn, cycle through the layer's
+ * features and draw each one.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * 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<len; i++) {
+ this.renderer.locked = (i !== (len - 1));
+ feature = this.features[i];
+ this.drawFeature(feature);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: display
+ * Hide or show the Layer
+ *
+ * Parameters:
+ * display - {Boolean}
+ */
+ display: function(display) {
+ OpenLayers.Layer.prototype.display.apply(this, arguments);
+ // we need to set the display style of the root in case it is attached
+ // to a foreign layer
+ var currentDisplay = this.div.style.display;
+ if(currentDisplay != this.renderer.root.style.display) {
+ this.renderer.root.style.display = currentDisplay;
+ }
+ },
+
+ /**
+ * APIMethod: addFeatures
+ * Add Features to the layer.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ * 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<len; i++) {
+ if (i != (features.length - 1)) {
+ this.renderer.locked = true;
+ } else {
+ this.renderer.locked = false;
+ }
+ var feature = features[i];
+
+ if (this.geometryType &&
+ !(feature.geometry instanceof this.geometryType)) {
+ throw new TypeError('addFeatures: component should be an ' +
+ this.geometryType.prototype.CLASS_NAME);
+ }
+
+ //give feature reference to its layer
+ feature.layer = this;
+
+ if (!feature.style && this.style) {
+ feature.style = OpenLayers.Util.extend({}, this.style);
+ }
+
+ if (notify) {
+ if(this.events.triggerEvent("beforefeatureadded",
+ {feature: feature}) === false) {
+ continue;
+ }
+ this.preFeatureInsert(feature);
+ }
+
+ featuresAdded.push(feature);
+ this.features.push(feature);
+ this.drawFeature(feature);
+
+ if (notify) {
+ this.events.triggerEvent("featureadded", {
+ feature: feature
+ });
+ this.onFeatureInsert(feature);
+ }
+ }
+
+ if(notify) {
+ this.events.triggerEvent("featuresadded", {features: featuresAdded});
+ }
+ },
+
+
+ /**
+ * APIMethod: removeFeatures
+ * Remove features from the layer. This erases any drawn features and
+ * removes them from the layer's control. The beforefeatureremoved
+ * and featureremoved events will be triggered for each feature. The
+ * featuresremoved event will be triggered after all features have
+ * been removed. To supress event triggering, use the silent option.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} 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(<OpenLayers.Feature.Vector>)} 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 - {<OpenLayers.Feature.Vector>}
+ * 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(<OpenLayers.Feature.Vector>)}
+ */
+ 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:
+ * {<OpenLayers.Feature.Vector>} 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:
+ * {<OpenLayers.Feature.Vector>} 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<len; ++i) {
+ if(this.features[i][property] == value) {
+ feature = this.features[i];
+ break;
+ }
+ }
+ return feature;
+ },
+
+ /**
+ * APIMethod: getFeatureById
+ * Given a feature id, return the feature if it exists in the features array
+ *
+ * Parameters:
+ * featureId - {String}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} 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:
+ * {<OpenLayers.Feature.Vector>} 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({<OpenLayers.Feature.Vector>}) 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 - {<OpenLayers.Feature.Vector>}
+ */
+ 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 - {<OpenLayers.Feature.Vector>}
+ */
+ preFeatureInsert: function(feature) {
+ },
+
+ /**
+ * APIMethod: getDataExtent
+ * Calculates the max extent which includes all of the features.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} 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<len; i++) {
+ geometry = features[i].geometry;
+ if (geometry) {
+ if (maxExtent === null) {
+ maxExtent = new OpenLayers.Bounds();
+ }
+ maxExtent.extend(geometry.getBounds());
+ }
+ }
+ }
+ return maxExtent;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Vector"
+});
+/* ======================================================================
+ OpenLayers/Layer/Vector/RootContainer.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.Vector.RootContainer
+ * A special layer type to combine multiple vector layers inside a single
+ * renderer root container. This class is not supposed to be instantiated
+ * from user space, it is a helper class for controls that require event
+ * processing for multiple vector layers.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Vector>
+ */
+OpenLayers.Layer.Vector.RootContainer = OpenLayers.Class(OpenLayers.Layer.Vector, {
+
+ /**
+ * Property: displayInLayerSwitcher
+ * Set to false for this layer type
+ */
+ displayInLayerSwitcher: false,
+
+ /**
+ * APIProperty: layers
+ * Layers that are attached to this container. Required config option.
+ */
+ layers: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.Vector.RootContainer
+ * Create a new root container for multiple vector layer. This constructor
+ * is not supposed to be used from user space, it is only to be used by
+ * controls that need feature selection across multiple vector layers.
+ *
+ * Parameters:
+ * name - {String} A name for the layer
+ * options - {Object} Optional object with non-default properties to set on
+ * the layer.
+ *
+ * Required options properties:
+ * layers - {Array(<OpenLayers.Layer.Vector>)} The layers managed by this
+ * container
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Vector.RootContainer>} A new vector layer root
+ * container
+ */
+
+ /**
+ * Method: display
+ */
+ display: function() {},
+
+ /**
+ * Method: getFeatureFromEvent
+ * walk through the layers to find the feature returned by the event
+ *
+ * Parameters:
+ * evt - {Object} event object with a feature property
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>}
+ */
+ getFeatureFromEvent: function(evt) {
+ var layers = this.layers;
+ var feature;
+ for(var i=0; i<layers.length; i++) {
+ feature = layers[i].getFeatureFromEvent(evt);
+ if(feature) {
+ return feature;
+ }
+ }
+ },
+
+ /**
+ * Method: setMap
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.Vector.prototype.setMap.apply(this, arguments);
+ this.collectRoots();
+ map.events.register("changelayer", this, this.handleChangeLayer);
+ },
+
+ /**
+ * Method: removeMap
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ map.events.unregister("changelayer", this, this.handleChangeLayer);
+ this.resetRoots();
+ OpenLayers.Layer.Vector.prototype.removeMap.apply(this, arguments);
+ },
+
+ /**
+ * Method: collectRoots
+ * Collects the root nodes of all layers this control is configured with
+ * and moveswien the nodes to this control's layer
+ */
+ collectRoots: function() {
+ var layer;
+ // walk through all map layers, because we want to keep the order
+ for(var i=0; i<this.map.layers.length; ++i) {
+ layer = this.map.layers[i];
+ if(OpenLayers.Util.indexOf(this.layers, layer) != -1) {
+ layer.renderer.moveRoot(this.renderer);
+ }
+ }
+ },
+
+ /**
+ * Method: resetRoots
+ * Resets the root nodes back into the layers they belong to.
+ */
+ resetRoots: function() {
+ var layer;
+ for(var i=0; i<this.layers.length; ++i) {
+ layer = this.layers[i];
+ if(this.renderer && layer.renderer.getRenderLayerId() == this.id) {
+ this.renderer.moveRoot(layer.renderer);
+ }
+ }
+ },
+
+ /**
+ * Method: handleChangeLayer
+ * Event handler for the map's changelayer event. We need to rebuild
+ * this container's layer dom if order of one of its layers changes.
+ * This handler is added with the setMap method, and removed with the
+ * removeMap method.
+ *
+ * Parameters:
+ * evt - {Object}
+ */
+ handleChangeLayer: function(evt) {
+ var layer = evt.layer;
+ if(evt.property == "order" &&
+ OpenLayers.Util.indexOf(this.layers, layer) != -1) {
+ this.resetRoots();
+ this.collectRoots();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Vector.RootContainer"
+});
+/* ======================================================================
+ OpenLayers/Control/SelectFeature.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/Feature/Vector.js
+ * @requires OpenLayers/Handler/Feature.js
+ * @requires OpenLayers/Layer/Vector/RootContainer.js
+ */
+
+/**
+ * Class: OpenLayers.Control.SelectFeature
+ * The SelectFeature control selects vector features from a given layer on
+ * click or hover.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.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 <OpenLayers.Control.events>):
+ * beforefeaturehighlighted - Triggered before a feature is highlighted
+ * featurehighlighted - Triggered when a feature is highlighted
+ * featureunhighlighted - Triggered when a feature is unhighlighted
+ * boxselectionstart - Triggered before box selection starts
+ * boxselectionend - Triggered after box selection ends
+ */
+
+ /**
+ * Property: multipleKey
+ * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
+ * the <multiple> property to true. Default is null.
+ */
+ multipleKey: null,
+
+ /**
+ * Property: toggleKey
+ * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
+ * the <toggle> property to true. Default is null.
+ */
+ toggleKey: null,
+
+ /**
+ * APIProperty: multiple
+ * {Boolean} Allow selection of multiple geometries. Default is false.
+ */
+ multiple: false,
+
+ /**
+ * 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 false. Only
+ * has meaning if hover is false.
+ */
+ toggle: false,
+
+ /**
+ * APIProperty: hover
+ * {Boolean} Select on mouse over and deselect on mouse out. If true, this
+ * ignores clicks and only listens to mouse moves.
+ */
+ hover: false,
+
+ /**
+ * APIProperty: highlightOnly
+ * {Boolean} If true do not actually select features (that is place them in
+ * the layer's selected features array), just highlight them. This property
+ * has no effect if hover is false. Defaults to false.
+ */
+ highlightOnly: false,
+
+ /**
+ * APIProperty: box
+ * {Boolean} Allow feature selection by drawing a box.
+ */
+ box: false,
+
+ /**
+ * Property: onBeforeSelect
+ * {Function} Optional function to be called before a feature is selected.
+ * The function should expect to be called with a feature.
+ */
+ onBeforeSelect: function() {},
+
+ /**
+ * APIProperty: onSelect
+ * {Function} Optional function to be called when a feature is selected.
+ * The function should expect to be called with a feature.
+ */
+ onSelect: function() {},
+
+ /**
+ * APIProperty: onUnselect
+ * {Function} Optional function to be called when a feature is unselected.
+ * The function should expect to be called with a feature.
+ */
+ onUnselect: function() {},
+
+ /**
+ * Property: scope
+ * {Object} The scope to use with the onBeforeSelect, onSelect, onUnselect
+ * callbacks. If null the scope will be this control.
+ */
+ scope: null,
+
+ /**
+ * APIProperty: geometryTypes
+ * {Array(String)} To restrict selecting to a limited set of geometry types,
+ * send a list of strings corresponding to the geometry class names.
+ */
+ geometryTypes: null,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>} The vector layer with a common renderer
+ * root for all layers this control is configured with (if an array of
+ * layers was passed to the constructor), or the vector layer the control
+ * was configured with (if a single layer was passed to the constructor).
+ */
+ layer: null,
+
+ /**
+ * Property: layers
+ * {Array(<OpenLayers.Layer.Vector>)} The layers this control will work on,
+ * or null if the control was configured with a single layer
+ */
+ layers: null,
+
+ /**
+ * APIProperty: callbacks
+ * {Object} The functions that are sent to the handlers.feature for callback
+ */
+ callbacks: null,
+
+ /**
+ * APIProperty: selectStyle
+ * {Object} Hash of styles
+ */
+ selectStyle: null,
+
+ /**
+ * Property: renderIntent
+ * {String} key used to retrieve the select style from the layer's
+ * style map.
+ */
+ renderIntent: "select",
+
+ /**
+ * Property: handlers
+ * {Object} Object with references to multiple <OpenLayers.Handler>
+ * instances.
+ */
+ handlers: null,
+
+ /**
+ * Constructor: OpenLayers.Control.SelectFeature
+ * Create a new control for selecting features.
+ *
+ * Parameters:
+ * layers - {<OpenLayers.Layer.Vector>}, or an array of vector layers. The
+ * layer(s) this control will select features from.
+ * options - {Object}
+ */
+ initialize: function(layers, options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+
+ if(this.scope === null) {
+ this.scope = this;
+ }
+ this.initLayer(layers);
+ var callbacks = {
+ click: this.clickFeature,
+ clickout: this.clickoutFeature
+ };
+ if (this.hover) {
+ callbacks.over = this.overFeature;
+ callbacks.out = this.outFeature;
+ }
+
+ this.callbacks = OpenLayers.Util.extend(callbacks, this.callbacks);
+ this.handlers = {
+ feature: new OpenLayers.Handler.Feature(
+ this, this.layer, this.callbacks,
+ {geometryTypes: this.geometryTypes}
+ )
+ };
+
+ if (this.box) {
+ this.handlers.box = new OpenLayers.Handler.Box(
+ this, {done: this.selectBox},
+ {boxDivClassName: "olHandlerBoxSelectFeature"}
+ );
+ }
+ },
+
+ /**
+ * Method: initLayer
+ * Assign the layer property. If layers is an array, we need to use
+ * a RootContainer.
+ *
+ * Parameters:
+ * layers - {<OpenLayers.Layer.Vector>}, or an array of vector layers.
+ */
+ initLayer: function(layers) {
+ if(OpenLayers.Util.isArray(layers)) {
+ this.layers = layers;
+ this.layer = new OpenLayers.Layer.Vector.RootContainer(
+ this.id + "_container", {
+ layers: layers
+ }
+ );
+ } else {
+ this.layer = layers;
+ }
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ if(this.active && this.layers) {
+ this.map.removeLayer(this.layer);
+ }
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ if(this.layers) {
+ this.layer.destroy();
+ }
+ },
+
+ /**
+ * Method: activate
+ * Activates the control.
+ *
+ * Returns:
+ * {Boolean} The control was effectively activated.
+ */
+ activate: function () {
+ if (!this.active) {
+ if(this.layers) {
+ this.map.addLayer(this.layer);
+ }
+ this.handlers.feature.activate();
+ if(this.box && this.handlers.box) {
+ this.handlers.box.activate();
+ }
+ }
+ return OpenLayers.Control.prototype.activate.apply(
+ this, arguments
+ );
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivates the control.
+ *
+ * Returns:
+ * {Boolean} The control was effectively deactivated.
+ */
+ deactivate: function () {
+ if (this.active) {
+ this.handlers.feature.deactivate();
+ if(this.handlers.box) {
+ this.handlers.box.deactivate();
+ }
+ if(this.layers) {
+ this.map.removeLayer(this.layer);
+ }
+ }
+ return OpenLayers.Control.prototype.deactivate.apply(
+ this, arguments
+ );
+ },
+
+ /**
+ * Method: unselectAll
+ * Unselect all selected features. To unselect all except for a single
+ * feature, set the options.except property to the feature.
+ *
+ * Parameters:
+ * options - {Object} Optional configuration object.
+ */
+ unselectAll: function(options) {
+ // we'll want an option to supress notification here
+ var layers = this.layers || [this.layer],
+ layer, feature, l, numExcept;
+ for(l=0; l<layers.length; ++l) {
+ layer = layers[l];
+ numExcept = 0;
+ //layer.selectedFeatures is null when layer is destroyed and
+ //one of it's preremovelayer listener calls setLayer
+ //with another layer on this control
+ if(layer.selectedFeatures != null) {
+ while(layer.selectedFeatures.length > numExcept) {
+ feature = layer.selectedFeatures[numExcept];
+ if(!options || options.except != feature) {
+ this.unselect(feature);
+ } else {
+ ++numExcept;
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: clickFeature
+ * Called on click in a feature
+ * Only responds if this.hover is false.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ clickFeature: function(feature) {
+ if(!this.hover) {
+ var selected = (OpenLayers.Util.indexOf(
+ feature.layer.selectedFeatures, feature) > -1);
+ if(selected) {
+ if(this.toggleSelect()) {
+ this.unselect(feature);
+ } else if(!this.multipleSelect()) {
+ this.unselectAll({except: feature});
+ }
+ } else {
+ if(!this.multipleSelect()) {
+ this.unselectAll({except: feature});
+ }
+ this.select(feature);
+ }
+ }
+ },
+
+ /**
+ * Method: multipleSelect
+ * Allow for multiple selected features based on <multiple> property and
+ * <multipleKey> event modifier.
+ *
+ * Returns:
+ * {Boolean} Allow for multiple selected features.
+ */
+ multipleSelect: function() {
+ return this.multiple || (this.handlers.feature.evt &&
+ this.handlers.feature.evt[this.multipleKey]);
+ },
+
+ /**
+ * Method: toggleSelect
+ * Event should toggle the selected state of a feature based on <toggle>
+ * property and <toggleKey> event modifier.
+ *
+ * Returns:
+ * {Boolean} Toggle the selected state of a feature.
+ */
+ toggleSelect: function() {
+ return this.toggle || (this.handlers.feature.evt &&
+ this.handlers.feature.evt[this.toggleKey]);
+ },
+
+ /**
+ * Method: clickoutFeature
+ * Called on click outside a previously clicked (selected) feature.
+ * Only responds if this.hover is false.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Vector.Feature>}
+ */
+ clickoutFeature: function(feature) {
+ if(!this.hover && this.clickout) {
+ this.unselectAll();
+ }
+ },
+
+ /**
+ * Method: overFeature
+ * Called on over a feature.
+ * Only responds if this.hover is true.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ overFeature: function(feature) {
+ var layer = feature.layer;
+ if(this.hover) {
+ if(this.highlightOnly) {
+ this.highlight(feature);
+ } else if(OpenLayers.Util.indexOf(
+ layer.selectedFeatures, feature) == -1) {
+ this.select(feature);
+ }
+ }
+ },
+
+ /**
+ * Method: outFeature
+ * Called on out of a selected feature.
+ * Only responds if this.hover is true.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ outFeature: function(feature) {
+ if(this.hover) {
+ if(this.highlightOnly) {
+ // we do nothing if we're not the last highlighter of the
+ // feature
+ if(feature._lastHighlighter == this.id) {
+ // if another select control had highlighted the feature before
+ // we did it ourself then we use that control to highlight the
+ // feature as it was before we highlighted it, else we just
+ // unhighlight it
+ if(feature._prevHighlighter &&
+ feature._prevHighlighter != this.id) {
+ delete feature._lastHighlighter;
+ var control = this.map.getControl(
+ feature._prevHighlighter);
+ if(control) {
+ control.highlight(feature);
+ }
+ } else {
+ this.unhighlight(feature);
+ }
+ }
+ } else {
+ this.unselect(feature);
+ }
+ }
+ },
+
+ /**
+ * Method: highlight
+ * Redraw feature with the select style.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ highlight: function(feature) {
+ var layer = feature.layer;
+ var cont = this.events.triggerEvent("beforefeaturehighlighted", {
+ feature : feature
+ });
+ if(cont !== false) {
+ feature._prevHighlighter = feature._lastHighlighter;
+ feature._lastHighlighter = this.id;
+ var style = this.selectStyle || this.renderIntent;
+ layer.drawFeature(feature, style);
+ this.events.triggerEvent("featurehighlighted", {feature : feature});
+ }
+ },
+
+ /**
+ * Method: unhighlight
+ * Redraw feature with the "default" style
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ unhighlight: function(feature) {
+ var layer = feature.layer;
+ // three cases:
+ // 1. there's no other highlighter, in that case _prev is undefined,
+ // and we just need to undef _last
+ // 2. another control highlighted the feature after we did it, in
+ // that case _last references this other control, and we just
+ // need to undef _prev
+ // 3. another control highlighted the feature before we did it, in
+ // that case _prev references this other control, and we need to
+ // set _last to _prev and undef _prev
+ if(feature._prevHighlighter == undefined) {
+ delete feature._lastHighlighter;
+ } else if(feature._prevHighlighter == this.id) {
+ delete feature._prevHighlighter;
+ } else {
+ feature._lastHighlighter = feature._prevHighlighter;
+ delete feature._prevHighlighter;
+ }
+ layer.drawFeature(feature, feature.style || feature.layer.style ||
+ "default");
+ this.events.triggerEvent("featureunhighlighted", {feature : feature});
+ },
+
+ /**
+ * Method: select
+ * Add feature to the layer's selectedFeature array, render the feature as
+ * selected, and call the onSelect function.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ select: function(feature) {
+ var cont = this.onBeforeSelect.call(this.scope, feature);
+ var layer = feature.layer;
+ if(cont !== false) {
+ cont = layer.events.triggerEvent("beforefeatureselected", {
+ feature: feature
+ });
+ if(cont !== false) {
+ layer.selectedFeatures.push(feature);
+ this.highlight(feature);
+ // if the feature handler isn't involved in the feature
+ // selection (because the box handler is used or the
+ // feature is selected programatically) we fake the
+ // feature handler to allow unselecting on click
+ if(!this.handlers.feature.lastFeature) {
+ this.handlers.feature.lastFeature = layer.selectedFeatures[0];
+ }
+ layer.events.triggerEvent("featureselected", {feature: feature});
+ this.onSelect.call(this.scope, feature);
+ }
+ }
+ },
+
+ /**
+ * Method: unselect
+ * Remove feature from the layer's selectedFeature array, render the feature as
+ * normal, and call the onUnselect function.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ unselect: function(feature) {
+ var layer = feature.layer;
+ // Store feature style for restoration later
+ this.unhighlight(feature);
+ OpenLayers.Util.removeItem(layer.selectedFeatures, feature);
+ layer.events.triggerEvent("featureunselected", {feature: feature});
+ this.onUnselect.call(this.scope, feature);
+ },
+
+ /**
+ * Method: selectBox
+ * Callback from the handlers.box set up when <box> selection is true
+ * on.
+ *
+ * Parameters:
+ * position - {<OpenLayers.Bounds> || <OpenLayers.Pixel> }
+ */
+ selectBox: function(position) {
+ if (position instanceof OpenLayers.Bounds) {
+ var minXY = this.map.getLonLatFromPixel({
+ x: position.left,
+ y: position.bottom
+ });
+ var maxXY = this.map.getLonLatFromPixel({
+ x: position.right,
+ y: position.top
+ });
+ var bounds = new OpenLayers.Bounds(
+ minXY.lon, minXY.lat, maxXY.lon, maxXY.lat
+ );
+
+ // if multiple is false, first deselect currently selected features
+ if (!this.multipleSelect()) {
+ this.unselectAll();
+ }
+
+ // because we're using a box, we consider we want multiple selection
+ var prevMultiple = this.multiple;
+ this.multiple = true;
+ var layers = this.layers || [this.layer];
+ this.events.triggerEvent("boxselectionstart", {layers: layers});
+ var layer;
+ for(var l=0; l<layers.length; ++l) {
+ layer = layers[l];
+ for(var i=0, len = layer.features.length; i<len; ++i) {
+ var feature = layer.features[i];
+ // check if the feature is displayed
+ if (!feature.getVisibility()) {
+ continue;
+ }
+
+ if (this.geometryTypes == null || OpenLayers.Util.indexOf(
+ this.geometryTypes, feature.geometry.CLASS_NAME) > -1) {
+ if (bounds.toGeometry().intersects(feature.geometry)) {
+ if (OpenLayers.Util.indexOf(layer.selectedFeatures, feature) == -1) {
+ this.select(feature);
+ }
+ }
+ }
+ }
+ }
+ this.multiple = prevMultiple;
+ this.events.triggerEvent("boxselectionend", {layers: layers});
+ }
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the control.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ this.handlers.feature.setMap(map);
+ if (this.box) {
+ this.handlers.box.setMap(map);
+ }
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: setLayer
+ * Attach a new layer to the control, overriding any existing layers.
+ *
+ * Parameters:
+ * layers - Array of {<OpenLayers.Layer.Vector>} or a single
+ * {<OpenLayers.Layer.Vector>}
+ */
+ setLayer: function(layers) {
+ var isActive = this.active;
+ this.unselectAll();
+ this.deactivate();
+ if(this.layers) {
+ this.layer.destroy();
+ this.layers = null;
+ }
+ this.initLayer(layers);
+ this.handlers.feature.layer = this.layer;
+ if (isActive) {
+ this.activate();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.SelectFeature"
+});
+/* ======================================================================
+ OpenLayers/Handler/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/Handler.js
+ * @requires OpenLayers/Geometry/Point.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Point
+ * Handler to draw a point on the map. Point is displayed on activation,
+ * moves on mouse move, and is finished on mouse up. The handler triggers
+ * callbacks for 'done', 'cancel', and 'modify'. The modify callback is
+ * called with each change in the sketch and will receive the latest point
+ * drawn. Create a new instance with the <OpenLayers.Handler.Point>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Point = OpenLayers.Class(OpenLayers.Handler, {
+
+ /**
+ * Property: point
+ * {<OpenLayers.Feature.Vector>} The currently drawn point
+ */
+ point: null,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>} The temporary drawing layer
+ */
+ layer: null,
+
+ /**
+ * APIProperty: multi
+ * {Boolean} Cast features to multi-part geometries before passing to the
+ * layer. Default is false.
+ */
+ multi: false,
+
+ /**
+ * APIProperty: citeCompliant
+ * {Boolean} If set to true, coordinates of features drawn in a map extent
+ * crossing the date line won't exceed the world bounds. Default is false.
+ */
+ citeCompliant: false,
+
+ /**
+ * Property: mouseDown
+ * {Boolean} The mouse is down
+ */
+ mouseDown: false,
+
+ /**
+ * Property: stoppedDown
+ * {Boolean} Indicate whether the last mousedown stopped the event
+ * propagation.
+ */
+ stoppedDown: null,
+
+ /**
+ * Property: lastDown
+ * {<OpenLayers.Pixel>} Location of the last mouse down
+ */
+ lastDown: null,
+
+ /**
+ * Property: lastUp
+ * {<OpenLayers.Pixel>}
+ */
+ lastUp: null,
+
+ /**
+ * APIProperty: persist
+ * {Boolean} Leave the feature rendered until destroyFeature is called.
+ * Default is false. If set to true, the feature remains rendered until
+ * destroyFeature is called, typically by deactivating the handler or
+ * starting another drawing.
+ */
+ persist: false,
+
+ /**
+ * APIProperty: stopDown
+ * {Boolean} Stop event propagation on mousedown. Must be false to
+ * allow "pan while drawing". Defaults to false.
+ */
+ stopDown: false,
+
+ /**
+ * APIPropery: stopUp
+ * {Boolean} Stop event propagation on mouse. Must be false to
+ * allow "pan while dragging". Defaults to fase.
+ */
+ stopUp: false,
+
+ /**
+ * Property: layerOptions
+ * {Object} Any optional properties to be set on the sketch layer.
+ */
+ layerOptions: null,
+
+ /**
+ * APIProperty: pixelTolerance
+ * {Number} Maximum number of pixels between down and up (mousedown
+ * and mouseup, or touchstart and touchend) for the handler to
+ * add a new point. If set to an integer value, if the
+ * displacement between down and up is great to this value
+ * no point will be added. Default value is 5.
+ */
+ pixelTolerance: 5,
+
+ /**
+ * Property: lastTouchPx
+ * {<OpenLayers.Pixel>} The last pixel used to know the distance between
+ * two touches (for double touch).
+ */
+ lastTouchPx: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Point
+ * Create a new point handler.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that owns this handler
+ * callbacks - {Object} An object with a properties whose values are
+ * functions. Various callbacks described below.
+ * options - {Object} An optional object with properties to be set on the
+ * handler
+ *
+ * Named callbacks:
+ * create - Called when a sketch is first created. Callback called with
+ * the creation point geometry and sketch feature.
+ * modify - Called with each move of a vertex with the vertex (point)
+ * geometry and the sketch feature.
+ * done - Called when the point drawing is finished. The callback will
+ * recieve a single argument, the point geometry.
+ * cancel - Called when the handler is deactivated while drawing. The
+ * cancel callback will receive a geometry.
+ */
+ initialize: function(control, callbacks, options) {
+ if(!(options && options.layerOptions && options.layerOptions.styleMap)) {
+ this.style = OpenLayers.Util.extend(OpenLayers.Feature.Vector.style['default'], {});
+ }
+
+ OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: activate
+ * turn on the handler
+ */
+ activate: function() {
+ if(!OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ return false;
+ }
+ // create temporary vector layer for rendering geometry sketch
+ // TBD: this could be moved to initialize/destroy - setting visibility here
+ var options = OpenLayers.Util.extend({
+ displayInLayerSwitcher: false,
+ // indicate that the temp vector layer will never be out of range
+ // without this, resolution properties must be specified at the
+ // map-level for this temporary layer to init its resolutions
+ // correctly
+ calculateInRange: OpenLayers.Function.True,
+ wrapDateLine: this.citeCompliant
+ }, this.layerOptions);
+ this.layer = new OpenLayers.Layer.Vector(this.CLASS_NAME, options);
+ this.map.addLayer(this.layer);
+ return true;
+ },
+
+ /**
+ * Method: createFeature
+ * Add temporary features
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} A pixel location on the map.
+ */
+ createFeature: function(pixel) {
+ var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
+ var geometry = new OpenLayers.Geometry.Point(
+ lonlat.lon, lonlat.lat
+ );
+ this.point = new OpenLayers.Feature.Vector(geometry);
+ this.callback("create", [this.point.geometry, this.point]);
+ this.point.geometry.clearBounds();
+ this.layer.addFeatures([this.point], {silent: true});
+ },
+
+ /**
+ * APIMethod: deactivate
+ * turn off the handler
+ */
+ deactivate: function() {
+ if(!OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ return false;
+ }
+ this.cancel();
+ // If a layer's map property is set to null, it means that that layer
+ // isn't added to the map. Since we ourself added the layer to the map
+ // in activate(), we can assume that if this.layer.map is null it means
+ // that the layer has been destroyed (as a result of map.destroy() for
+ // example.
+ if (this.layer.map != null) {
+ this.destroyFeature(true);
+ this.layer.destroy(false);
+ }
+ this.layer = null;
+ return true;
+ },
+
+ /**
+ * Method: destroyFeature
+ * Destroy the temporary geometries
+ *
+ * Parameters:
+ * force - {Boolean} Destroy even if persist is true.
+ */
+ destroyFeature: function(force) {
+ if(this.layer && (force || !this.persist)) {
+ this.layer.destroyFeatures();
+ }
+ this.point = null;
+ },
+
+ /**
+ * Method: destroyPersistedFeature
+ * Destroy the persisted feature.
+ */
+ destroyPersistedFeature: function() {
+ var layer = this.layer;
+ if(layer && layer.features.length > 1) {
+ this.layer.features[0].destroy();
+ }
+ },
+
+ /**
+ * Method: finalize
+ * Finish the geometry and call the "done" callback.
+ *
+ * Parameters:
+ * cancel - {Boolean} Call cancel instead of done callback. Default
+ * is false.
+ */
+ finalize: function(cancel) {
+ var key = cancel ? "cancel" : "done";
+ this.mouseDown = false;
+ this.lastDown = null;
+ this.lastUp = null;
+ this.lastTouchPx = null;
+ this.callback(key, [this.geometryClone()]);
+ this.destroyFeature(cancel);
+ },
+
+ /**
+ * APIMethod: cancel
+ * Finish the geometry and call the "cancel" callback.
+ */
+ cancel: function() {
+ this.finalize(true);
+ },
+
+ /**
+ * Method: click
+ * Handle clicks. Clicks are stopped from propagating to other listeners
+ * on map.events or other dom elements.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ click: function(evt) {
+ OpenLayers.Event.stop(evt);
+ return false;
+ },
+
+ /**
+ * Method: dblclick
+ * Handle double-clicks. Double-clicks are stopped from propagating to other
+ * listeners on map.events or other dom elements.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ dblclick: function(evt) {
+ OpenLayers.Event.stop(evt);
+ return false;
+ },
+
+ /**
+ * Method: modifyFeature
+ * Modify the existing geometry given a pixel location.
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} A pixel location on the map.
+ */
+ modifyFeature: function(pixel) {
+ if(!this.point) {
+ this.createFeature(pixel);
+ }
+ var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
+ this.point.geometry.x = lonlat.lon;
+ this.point.geometry.y = lonlat.lat;
+ this.callback("modify", [this.point.geometry, this.point, false]);
+ this.point.geometry.clearBounds();
+ this.drawFeature();
+ },
+
+ /**
+ * Method: drawFeature
+ * Render features on the temporary layer.
+ */
+ drawFeature: function() {
+ this.layer.drawFeature(this.point, this.style);
+ },
+
+ /**
+ * Method: getGeometry
+ * Return the sketch geometry. If <multi> is true, this will return
+ * a multi-part geometry.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>}
+ */
+ getGeometry: function() {
+ var geometry = this.point && this.point.geometry;
+ if(geometry && this.multi) {
+ geometry = new OpenLayers.Geometry.MultiPoint([geometry]);
+ }
+ return geometry;
+ },
+
+ /**
+ * Method: geometryClone
+ * Return a clone of the relevant geometry.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>}
+ */
+ geometryClone: function() {
+ var geom = this.getGeometry();
+ return geom && geom.clone();
+ },
+
+ /**
+ * Method: mousedown
+ * Handle mousedown.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ mousedown: function(evt) {
+ return this.down(evt);
+ },
+
+ /**
+ * Method: touchstart
+ * Handle touchstart.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ touchstart: function(evt) {
+ this.startTouch();
+ this.lastTouchPx = evt.xy;
+ return this.down(evt);
+ },
+
+ /**
+ * Method: mousemove
+ * Handle mousemove.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ mousemove: function(evt) {
+ return this.move(evt);
+ },
+
+ /**
+ * Method: touchmove
+ * Handle touchmove.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ touchmove: function(evt) {
+ this.lastTouchPx = evt.xy;
+ return this.move(evt);
+ },
+
+ /**
+ * Method: mouseup
+ * Handle mouseup.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ mouseup: function(evt) {
+ return this.up(evt);
+ },
+
+ /**
+ * Method: touchend
+ * Handle touchend.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ touchend: function(evt) {
+ evt.xy = this.lastTouchPx;
+ return this.up(evt);
+ },
+
+ /**
+ * Method: down
+ * Handle mousedown and touchstart. Adjust the geometry and redraw.
+ * Return determines whether to propagate the event on the map.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ down: function(evt) {
+ this.mouseDown = true;
+ this.lastDown = evt.xy;
+ if(!this.touch) { // no point displayed until up on touch devices
+ this.modifyFeature(evt.xy);
+ }
+ this.stoppedDown = this.stopDown;
+ return !this.stopDown;
+ },
+
+ /**
+ * Method: move
+ * Handle mousemove and touchmove. Adjust the geometry and redraw.
+ * Return determines whether to propagate the event on the map.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ move: function (evt) {
+ if(!this.touch // no point displayed until up on touch devices
+ && (!this.mouseDown || this.stoppedDown)) {
+ this.modifyFeature(evt.xy);
+ }
+ return true;
+ },
+
+ /**
+ * Method: up
+ * Handle mouseup and touchend. Send the latest point in the geometry to the control.
+ * Return determines whether to propagate the event on the map.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ up: function (evt) {
+ this.mouseDown = false;
+ this.stoppedDown = this.stopDown;
+
+ // check keyboard modifiers
+ if(!this.checkModifiers(evt)) {
+ return true;
+ }
+ // ignore double-clicks
+ if (this.lastUp && this.lastUp.equals(evt.xy)) {
+ return true;
+ }
+ if (this.lastDown && this.passesTolerance(this.lastDown, evt.xy,
+ this.pixelTolerance)) {
+ if (this.touch) {
+ this.modifyFeature(evt.xy);
+ }
+ if(this.persist) {
+ this.destroyPersistedFeature();
+ }
+ this.lastUp = evt.xy;
+ this.finalize();
+ return !this.stopUp;
+ } else {
+ return true;
+ }
+ },
+
+ /**
+ * Method: mouseout
+ * Handle mouse out. For better user experience reset mouseDown
+ * and stoppedDown when the mouse leaves the map viewport.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ */
+ mouseout: function(evt) {
+ if(OpenLayers.Util.mouseLeft(evt, this.map.viewPortDiv)) {
+ this.stoppedDown = this.stopDown;
+ this.mouseDown = false;
+ }
+ },
+
+ /**
+ * Method: passesTolerance
+ * Determine whether the event is within the optional pixel tolerance.
+ *
+ * Returns:
+ * {Boolean} The event is within the pixel tolerance (if specified).
+ */
+ passesTolerance: function(pixel1, pixel2, tolerance) {
+ var passes = true;
+
+ if (tolerance != null && pixel1 && pixel2) {
+ var dist = pixel1.distanceTo(pixel2);
+ if (dist > tolerance) {
+ passes = false;
+ }
+ }
+ return passes;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Point"
+});
+/* ======================================================================
+ OpenLayers/Handler/Path.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/Point.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/LineString.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Path
+ * Handler to draw a path on the map. Path is displayed on mouse down,
+ * moves on mouse move, and is finished on mouse up.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler.Point>
+ */
+OpenLayers.Handler.Path = OpenLayers.Class(OpenLayers.Handler.Point, {
+
+ /**
+ * Property: line
+ * {<OpenLayers.Feature.Vector>}
+ */
+ line: null,
+
+ /**
+ * APIProperty: maxVertices
+ * {Number} The maximum number of vertices which can be drawn by this
+ * handler. When the number of vertices reaches maxVertices, the
+ * geometry is automatically finalized. Default is null.
+ */
+ maxVertices: null,
+
+ /**
+ * Property: doubleTouchTolerance
+ * {Number} Maximum number of pixels between two touches for
+ * the gesture to be considered a "finalize feature" action.
+ * Default is 20.
+ */
+ doubleTouchTolerance: 20,
+
+ /**
+ * Property: freehand
+ * {Boolean} In freehand mode, the handler starts the path on mouse down,
+ * adds a point for every mouse move, and finishes the path on mouse up.
+ * Outside of freehand mode, a point is added to the path on every mouse
+ * click and double-click finishes the path.
+ */
+ freehand: false,
+
+ /**
+ * Property: freehandToggle
+ * {String} If set, freehandToggle is checked on mouse events and will set
+ * the freehand mode to the opposite of this.freehand. To disallow
+ * toggling between freehand and non-freehand mode, set freehandToggle to
+ * null. Acceptable toggle values are 'shiftKey', 'ctrlKey', and 'altKey'.
+ */
+ freehandToggle: 'shiftKey',
+
+ /**
+ * Property: timerId
+ * {Integer} The timer used to test the double touch.
+ */
+ timerId: null,
+
+ /**
+ * Property: redoStack
+ * {Array} Stack containing points removed with <undo>.
+ */
+ redoStack: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Path
+ * Create a new path hander
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that owns this handler
+ * callbacks - {Object} An object with a properties whose values are
+ * functions. Various callbacks described below.
+ * options - {Object} An optional object with properties to be set on the
+ * handler
+ *
+ * Named callbacks:
+ * create - Called when a sketch is first created. Callback called with
+ * the creation point geometry and sketch feature.
+ * modify - Called with each move of a vertex with the vertex (point)
+ * geometry and the sketch feature.
+ * point - Called as each point is added. Receives the new point geometry.
+ * done - Called when the point drawing is finished. The callback will
+ * recieve a single argument, the linestring geometry.
+ * cancel - Called when the handler is deactivated while drawing. The
+ * cancel callback will receive a geometry.
+ */
+
+ /**
+ * Method: createFeature
+ * Add temporary geometries
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} The initial pixel location for the new
+ * feature.
+ */
+ createFeature: function(pixel) {
+ var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
+ var geometry = new OpenLayers.Geometry.Point(
+ lonlat.lon, lonlat.lat
+ );
+ this.point = new OpenLayers.Feature.Vector(geometry);
+ this.line = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.LineString([this.point.geometry])
+ );
+ this.callback("create", [this.point.geometry, this.getSketch()]);
+ this.point.geometry.clearBounds();
+ this.layer.addFeatures([this.line, this.point], {silent: true});
+ },
+
+ /**
+ * Method: destroyFeature
+ * Destroy temporary geometries
+ *
+ * Parameters:
+ * force - {Boolean} Destroy even if persist is true.
+ */
+ destroyFeature: function(force) {
+ OpenLayers.Handler.Point.prototype.destroyFeature.call(
+ this, force);
+ this.line = null;
+ },
+
+ /**
+ * Method: destroyPersistedFeature
+ * Destroy the persisted feature.
+ */
+ destroyPersistedFeature: function() {
+ var layer = this.layer;
+ if(layer && layer.features.length > 2) {
+ this.layer.features[0].destroy();
+ }
+ },
+
+ /**
+ * Method: removePoint
+ * Destroy the temporary point.
+ */
+ removePoint: function() {
+ if(this.point) {
+ this.layer.removeFeatures([this.point]);
+ }
+ },
+
+ /**
+ * Method: addPoint
+ * Add point to geometry. Send the point index to override
+ * the behavior of LinearRing that disregards adding duplicate points.
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} The pixel location for the new point.
+ */
+ addPoint: function(pixel) {
+ this.layer.removeFeatures([this.point]);
+ var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
+ this.point = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat)
+ );
+ this.line.geometry.addComponent(
+ this.point.geometry, this.line.geometry.components.length
+ );
+ this.layer.addFeatures([this.point]);
+ this.callback("point", [this.point.geometry, this.getGeometry()]);
+ this.callback("modify", [this.point.geometry, this.getSketch()]);
+ this.drawFeature();
+ delete this.redoStack;
+ },
+
+ /**
+ * Method: insertXY
+ * Insert a point in the current sketch given x & y coordinates. The new
+ * point is inserted immediately before the most recently drawn point.
+ *
+ * Parameters:
+ * x - {Number} The x-coordinate of the point.
+ * y - {Number} The y-coordinate of the point.
+ */
+ insertXY: function(x, y) {
+ this.line.geometry.addComponent(
+ new OpenLayers.Geometry.Point(x, y),
+ this.getCurrentPointIndex()
+ );
+ this.drawFeature();
+ delete this.redoStack;
+ },
+
+ /**
+ * Method: insertDeltaXY
+ * Insert a point given offsets from the previously inserted point.
+ *
+ * Parameters:
+ * dx - {Number} The x-coordinate offset of the point.
+ * dy - {Number} The y-coordinate offset of the point.
+ */
+ insertDeltaXY: function(dx, dy) {
+ var previousIndex = this.getCurrentPointIndex() - 1;
+ var p0 = this.line.geometry.components[previousIndex];
+ if (p0 && !isNaN(p0.x) && !isNaN(p0.y)) {
+ this.insertXY(p0.x + dx, p0.y + dy);
+ }
+ },
+
+ /**
+ * Method: insertDirectionLength
+ * Insert a point in the current sketch given a direction and a length.
+ *
+ * Parameters:
+ * direction - {Number} Degrees clockwise from the positive x-axis.
+ * length - {Number} Distance from the previously drawn point.
+ */
+ insertDirectionLength: function(direction, length) {
+ direction *= Math.PI / 180;
+ var dx = length * Math.cos(direction);
+ var dy = length * Math.sin(direction);
+ this.insertDeltaXY(dx, dy);
+ },
+
+ /**
+ * Method: insertDeflectionLength
+ * Insert a point in the current sketch given a deflection and a length.
+ * The deflection should be degrees clockwise from the previously
+ * digitized segment.
+ *
+ * Parameters:
+ * deflection - {Number} Degrees clockwise from the previous segment.
+ * length - {Number} Distance from the previously drawn point.
+ */
+ insertDeflectionLength: function(deflection, length) {
+ var previousIndex = this.getCurrentPointIndex() - 1;
+ if (previousIndex > 0) {
+ var p1 = this.line.geometry.components[previousIndex];
+ var p0 = this.line.geometry.components[previousIndex-1];
+ var theta = Math.atan2(p1.y - p0.y, p1.x - p0.x);
+ this.insertDirectionLength(
+ (theta * 180 / Math.PI) + deflection, length
+ );
+ }
+ },
+
+ /**
+ * Method: getCurrentPointIndex
+ *
+ * Returns:
+ * {Number} The index of the most recently drawn point.
+ */
+ getCurrentPointIndex: function() {
+ return this.line.geometry.components.length - 1;
+ },
+
+
+ /**
+ * Method: undo
+ * Remove the most recently added point in the sketch geometry.
+ *
+ * Returns:
+ * {Boolean} A point was removed.
+ */
+ undo: function() {
+ var geometry = this.line.geometry;
+ var components = geometry.components;
+ var index = this.getCurrentPointIndex() - 1;
+ var target = components[index];
+ var undone = geometry.removeComponent(target);
+ if (undone) {
+ // On touch devices, set the current ("mouse location") point to
+ // match the last digitized point.
+ if (this.touch && index > 0) {
+ components = geometry.components; // safety
+ var lastpt = components[index - 1];
+ var curptidx = this.getCurrentPointIndex();
+ var curpt = components[curptidx];
+ curpt.x = lastpt.x;
+ curpt.y = lastpt.y;
+ }
+ if (!this.redoStack) {
+ this.redoStack = [];
+ }
+ this.redoStack.push(target);
+ this.drawFeature();
+ }
+ return undone;
+ },
+
+ /**
+ * Method: redo
+ * Reinsert the most recently removed point resulting from an <undo> call.
+ * The undo stack is deleted whenever a point is added by other means.
+ *
+ * Returns:
+ * {Boolean} A point was added.
+ */
+ redo: function() {
+ var target = this.redoStack && this.redoStack.pop();
+ if (target) {
+ this.line.geometry.addComponent(target, this.getCurrentPointIndex());
+ this.drawFeature();
+ }
+ return !!target;
+ },
+
+ /**
+ * Method: freehandMode
+ * Determine whether to behave in freehand mode or not.
+ *
+ * Returns:
+ * {Boolean}
+ */
+ freehandMode: function(evt) {
+ return (this.freehandToggle && evt[this.freehandToggle]) ?
+ !this.freehand : this.freehand;
+ },
+
+ /**
+ * Method: modifyFeature
+ * Modify the existing geometry given the new point
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} The updated pixel location for the latest
+ * point.
+ * drawing - {Boolean} Indicate if we're currently drawing.
+ */
+ modifyFeature: function(pixel, drawing) {
+ if(!this.line) {
+ this.createFeature(pixel);
+ }
+ var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
+ this.point.geometry.x = lonlat.lon;
+ this.point.geometry.y = lonlat.lat;
+ this.callback("modify", [this.point.geometry, this.getSketch(), drawing]);
+ this.point.geometry.clearBounds();
+ this.drawFeature();
+ },
+
+ /**
+ * Method: drawFeature
+ * Render geometries on the temporary layer.
+ */
+ drawFeature: function() {
+ this.layer.drawFeature(this.line, this.style);
+ this.layer.drawFeature(this.point, this.style);
+ },
+
+ /**
+ * Method: getSketch
+ * Return the sketch feature.
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>}
+ */
+ getSketch: function() {
+ return this.line;
+ },
+
+ /**
+ * Method: getGeometry
+ * Return the sketch geometry. If <multi> is true, this will return
+ * a multi-part geometry.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.LineString>}
+ */
+ getGeometry: function() {
+ var geometry = this.line && this.line.geometry;
+ if(geometry && this.multi) {
+ geometry = new OpenLayers.Geometry.MultiLineString([geometry]);
+ }
+ return geometry;
+ },
+
+ /**
+ * method: touchstart
+ * handle touchstart.
+ *
+ * parameters:
+ * evt - {event} the browser event
+ *
+ * returns:
+ * {boolean} allow event propagation
+ */
+ touchstart: function(evt) {
+ if (this.timerId &&
+ this.passesTolerance(this.lastTouchPx, evt.xy,
+ this.doubleTouchTolerance)) {
+ // double-tap, finalize the geometry
+ this.finishGeometry();
+ window.clearTimeout(this.timerId);
+ this.timerId = null;
+ return false;
+ } else {
+ if (this.timerId) {
+ window.clearTimeout(this.timerId);
+ this.timerId = null;
+ }
+ this.timerId = window.setTimeout(
+ OpenLayers.Function.bind(function() {
+ this.timerId = null;
+ }, this), 300);
+ return OpenLayers.Handler.Point.prototype.touchstart.call(this, evt);
+ }
+ },
+
+ /**
+ * Method: down
+ * Handle mousedown and touchstart. Add a new point to the geometry and
+ * render it. Return determines whether to propagate the event on the map.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ down: function(evt) {
+ var stopDown = this.stopDown;
+ if(this.freehandMode(evt)) {
+ stopDown = true;
+ if (this.touch) {
+ this.modifyFeature(evt.xy, !!this.lastUp);
+ OpenLayers.Event.stop(evt);
+ }
+ }
+ if (!this.touch && (!this.lastDown ||
+ !this.passesTolerance(this.lastDown, evt.xy,
+ this.pixelTolerance))) {
+ this.modifyFeature(evt.xy, !!this.lastUp);
+ }
+ this.mouseDown = true;
+ this.lastDown = evt.xy;
+ this.stoppedDown = stopDown;
+ return !stopDown;
+ },
+
+ /**
+ * Method: move
+ * Handle mousemove and touchmove. Adjust the geometry and redraw.
+ * Return determines whether to propagate the event on the map.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ move: function (evt) {
+ if(this.stoppedDown && this.freehandMode(evt)) {
+ if(this.persist) {
+ this.destroyPersistedFeature();
+ }
+ if(this.maxVertices && this.line &&
+ this.line.geometry.components.length === this.maxVertices) {
+ this.removePoint();
+ this.finalize();
+ } else {
+ this.addPoint(evt.xy);
+ }
+ return false;
+ }
+ if (!this.touch && (!this.mouseDown || this.stoppedDown)) {
+ this.modifyFeature(evt.xy, !!this.lastUp);
+ }
+ return true;
+ },
+
+ /**
+ * Method: up
+ * Handle mouseup and touchend. Send the latest point in the geometry to
+ * the control. Return determines whether to propagate the event on the map.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ up: function (evt) {
+ if (this.mouseDown && (!this.lastUp || !this.lastUp.equals(evt.xy))) {
+ if(this.stoppedDown && this.freehandMode(evt)) {
+ if (this.persist) {
+ this.destroyPersistedFeature();
+ }
+ this.removePoint();
+ this.finalize();
+ } else {
+ if (this.passesTolerance(this.lastDown, evt.xy,
+ this.pixelTolerance)) {
+ if (this.touch) {
+ this.modifyFeature(evt.xy);
+ }
+ if(this.lastUp == null && this.persist) {
+ this.destroyPersistedFeature();
+ }
+ this.addPoint(evt.xy);
+ this.lastUp = evt.xy;
+ if(this.line.geometry.components.length === this.maxVertices + 1) {
+ this.finishGeometry();
+ }
+ }
+ }
+ }
+ this.stoppedDown = this.stopDown;
+ this.mouseDown = false;
+ return !this.stopUp;
+ },
+
+ /**
+ * APIMethod: finishGeometry
+ * Finish the geometry and send it back to the control.
+ */
+ finishGeometry: function() {
+ var index = this.line.geometry.components.length - 1;
+ this.line.geometry.removeComponent(this.line.geometry.components[index]);
+ this.removePoint();
+ this.finalize();
+ },
+
+ /**
+ * Method: dblclick
+ * Handle double-clicks.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ dblclick: function(evt) {
+ if(!this.freehandMode(evt)) {
+ this.finishGeometry();
+ }
+ return false;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Path"
+});
+/* ======================================================================
+ OpenLayers/Control/Attribution.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.Attribution
+ * The attribution control adds attribution from layers to the map display.
+ * It uses 'attribution' property of each layer.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Attribution =
+ OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: separator
+ * {String} String used to separate layers.
+ */
+ separator: ", ",
+
+ /**
+ * APIProperty: template
+ * {String} Template for the attribution. This has to include the substring
+ * "${layers}", which will be replaced by the layer specific
+ * attributions, separated by <separator>. The default is "${layers}".
+ */
+ template: "${layers}",
+
+ /**
+ * Constructor: OpenLayers.Control.Attribution
+ *
+ * Parameters:
+ * options - {Object} Options for control.
+ */
+
+ /**
+ * Method: destroy
+ * Destroy control.
+ */
+ destroy: function() {
+ this.map.events.un({
+ "removelayer": this.updateAttribution,
+ "addlayer": this.updateAttribution,
+ "changelayer": this.updateAttribution,
+ "changebaselayer": this.updateAttribution,
+ scope: this
+ });
+
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: draw
+ * Initialize control.
+ *
+ * Returns:
+ * {DOMElement} A reference to the DIV DOMElement containing the control
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+
+ this.map.events.on({
+ 'changebaselayer': this.updateAttribution,
+ 'changelayer': this.updateAttribution,
+ 'addlayer': this.updateAttribution,
+ 'removelayer': this.updateAttribution,
+ scope: this
+ });
+ this.updateAttribution();
+
+ return this.div;
+ },
+
+ /**
+ * Method: updateAttribution
+ * Update attribution string.
+ */
+ updateAttribution: function() {
+ var attributions = [];
+ if (this.map && this.map.layers) {
+ for(var i=0, len=this.map.layers.length; i<len; i++) {
+ var layer = this.map.layers[i];
+ if (layer.attribution && layer.getVisibility()) {
+ // add attribution only if attribution text is unique
+ if (OpenLayers.Util.indexOf(
+ attributions, layer.attribution) === -1) {
+ attributions.push( layer.attribution );
+ }
+ }
+ }
+ this.div.innerHTML = OpenLayers.String.format(this.template, {
+ layers: attributions.join(this.separator)
+ });
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Attribution"
+});
+/* ======================================================================
+ OpenLayers/Kinetic.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
+ */
+
+OpenLayers.Kinetic = OpenLayers.Class({
+
+ /**
+ * Property: threshold
+ * In most cases changing the threshold isn't needed.
+ * In px/ms, default to 0.
+ */
+ threshold: 0,
+
+ /**
+ * Property: deceleration
+ * {Float} the deseleration in px/ms², default to 0.0035.
+ */
+ deceleration: 0.0035,
+
+ /**
+ * Property: nbPoints
+ * {Integer} the number of points we use to calculate the kinetic
+ * initial values.
+ */
+ nbPoints: 100,
+
+ /**
+ * Property: delay
+ * {Float} time to consider to calculate the kinetic initial values.
+ * In ms, default to 200.
+ */
+ delay: 200,
+
+ /**
+ * Property: points
+ * List of points use to calculate the kinetic initial values.
+ */
+ points: undefined,
+
+ /**
+ * Property: timerId
+ * ID of the timer.
+ */
+ timerId: undefined,
+
+ /**
+ * Constructor: OpenLayers.Kinetic
+ *
+ * Parameters:
+ * options - {Object}
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ },
+
+ /**
+ * Method: begin
+ * Begins the dragging.
+ */
+ begin: function() {
+ OpenLayers.Animation.stop(this.timerId);
+ this.timerId = undefined;
+ this.points = [];
+ },
+
+ /**
+ * Method: update
+ * Updates during the dragging.
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} The new position.
+ */
+ update: function(xy) {
+ this.points.unshift({xy: xy, tick: new Date().getTime()});
+ if (this.points.length > this.nbPoints) {
+ this.points.pop();
+ }
+ },
+
+ /**
+ * Method: end
+ * Ends the dragging, start the kinetic.
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} The last position.
+ *
+ * Returns:
+ * {Object} An object with two properties: "speed", and "theta". The
+ * "speed" and "theta" values are to be passed to the move
+ * function when starting the animation.
+ */
+ end: function(xy) {
+ var last, now = new Date().getTime();
+ for (var i = 0, l = this.points.length, point; i < l; i++) {
+ point = this.points[i];
+ if (now - point.tick > this.delay) {
+ break;
+ }
+ last = point;
+ }
+ if (!last) {
+ return;
+ }
+ var time = new Date().getTime() - last.tick;
+ var dist = Math.sqrt(Math.pow(xy.x - last.xy.x, 2) +
+ Math.pow(xy.y - last.xy.y, 2));
+ var speed = dist / time;
+ if (speed == 0 || speed < this.threshold) {
+ return;
+ }
+ var theta = Math.asin((xy.y - last.xy.y) / dist);
+ if (last.xy.x <= xy.x) {
+ theta = Math.PI - theta;
+ }
+ return {speed: speed, theta: theta};
+ },
+
+ /**
+ * Method: move
+ * Launch the kinetic move pan.
+ *
+ * Parameters:
+ * info - {Object} An object with two properties, "speed", and "theta".
+ * These values are those returned from the "end" call.
+ * callback - {Function} Function called on every step of the animation,
+ * receives x, y (values to pan), end (is the last point).
+ */
+ move: function(info, callback) {
+ var v0 = info.speed;
+ var fx = Math.cos(info.theta);
+ var fy = -Math.sin(info.theta);
+
+ var initialTime = new Date().getTime();
+
+ var lastX = 0;
+ var lastY = 0;
+
+ var timerCallback = function() {
+ if (this.timerId == null) {
+ return;
+ }
+
+ var t = new Date().getTime() - initialTime;
+
+ var p = (-this.deceleration * Math.pow(t, 2)) / 2.0 + v0 * t;
+ var x = p * fx;
+ var y = p * fy;
+
+ var args = {};
+ args.end = false;
+ var v = -this.deceleration * t + v0;
+
+ if (v <= 0) {
+ OpenLayers.Animation.stop(this.timerId);
+ this.timerId = null;
+ args.end = true;
+ }
+
+ args.x = x - lastX;
+ args.y = y - lastY;
+ lastX = x;
+ lastY = y;
+ callback(args.x, args.y, args.end);
+ };
+
+ this.timerId = OpenLayers.Animation.start(
+ OpenLayers.Function.bind(timerCallback, this)
+ );
+ },
+
+ CLASS_NAME: "OpenLayers.Kinetic"
+});
+/* ======================================================================
+ OpenLayers/Layer/WMS.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.WMS
+ * Instances of OpenLayers.Layer.WMS are used to display data from OGC Web
+ * Mapping Services. Create a new WMS layer with the <OpenLayers.Layer.WMS>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.WMS = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * Constant: DEFAULT_PARAMS
+ * {Object} Hashtable of default parameter key/value pairs
+ */
+ DEFAULT_PARAMS: { service: "WMS",
+ version: "1.1.1",
+ request: "GetMap",
+ styles: "",
+ format: "image/jpeg"
+ },
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} Default is true for WMS layer
+ */
+ isBaseLayer: true,
+
+ /**
+ * APIProperty: encodeBBOX
+ * {Boolean} Should the BBOX commas be encoded? The WMS spec says 'no',
+ * but some services want it that way. Default false.
+ */
+ encodeBBOX: false,
+
+ /**
+ * APIProperty: noMagic
+ * {Boolean} If true, the image format will not be automagicaly switched
+ * from image/jpeg to image/png or image/gif when using
+ * TRANSPARENT=TRUE. Also isBaseLayer will not changed by the
+ * constructor. Default false.
+ */
+ noMagic: false,
+
+ /**
+ * Property: yx
+ * {Object} Keys in this object are EPSG codes for which the axis order
+ * is to be reversed (yx instead of xy, LatLon instead of LonLat), with
+ * true as value. This is only relevant for WMS versions >= 1.3.0, and
+ * only if yx is not set in <OpenLayers.Projection.defaults> for the
+ * used projection.
+ */
+ yx: {},
+
+ /**
+ * Constructor: OpenLayers.Layer.WMS
+ * Create a new WMS layer object
+ *
+ * Examples:
+ *
+ * The code below creates a simple WMS layer using the image/jpeg format.
+ * (code)
+ * var wms = new OpenLayers.Layer.WMS("NASA Global Mosaic",
+ * "http://wms.jpl.nasa.gov/wms.cgi",
+ * {layers: "modis,global_mosaic"});
+ * (end)
+ * Note the 3rd argument (params). Properties added to this object will be
+ * added to the WMS GetMap requests used for this layer's tiles. The only
+ * mandatory parameter is "layers". Other common WMS params include
+ * "transparent", "styles" and "format". Note that the "srs" param will
+ * always be ignored. Instead, it will be derived from the baseLayer's or
+ * map's projection.
+ *
+ * The code below creates a transparent WMS layer with additional options.
+ * (code)
+ * var wms = new OpenLayers.Layer.WMS("NASA Global Mosaic",
+ * "http://wms.jpl.nasa.gov/wms.cgi",
+ * {
+ * layers: "modis,global_mosaic",
+ * transparent: true
+ * }, {
+ * opacity: 0.5,
+ * singleTile: true
+ * });
+ * (end)
+ * Note that by default, a WMS layer is configured as baseLayer. Setting
+ * the "transparent" param to true will apply some magic (see <noMagic>).
+ * The default image format changes from image/jpeg to image/png, and the
+ * layer is not configured as baseLayer.
+ *
+ * Parameters:
+ * name - {String} A name for the layer
+ * url - {String} Base url for the WMS
+ * (e.g. http://wms.jpl.nasa.gov/wms.cgi)
+ * params - {Object} An object with key/value pairs representing the
+ * GetMap query string parameters and parameter values.
+ * options - {Object} Hashtable of extra options to tag onto the layer.
+ * These options include all properties listed above, plus the ones
+ * inherited from superclasses.
+ */
+ initialize: function(name, url, params, options) {
+ var newArguments = [];
+ //uppercase params
+ params = OpenLayers.Util.upperCaseObject(params);
+ if (parseFloat(params.VERSION) >= 1.3 && !params.EXCEPTIONS) {
+ params.EXCEPTIONS = "INIMAGE";
+ }
+ newArguments.push(name, url, params, options);
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments);
+ OpenLayers.Util.applyDefaults(
+ this.params,
+ OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS)
+ );
+
+
+ //layer is transparent
+ if (!this.noMagic && this.params.TRANSPARENT &&
+ this.params.TRANSPARENT.toString().toLowerCase() == "true") {
+
+ // unless explicitly set in options, make layer an overlay
+ if ( (options == null) || (!options.isBaseLayer) ) {
+ this.isBaseLayer = false;
+ }
+
+ // jpegs can never be transparent, so intelligently switch the
+ // format, depending on the browser's capabilities
+ if (this.params.FORMAT == "image/jpeg") {
+ this.params.FORMAT = OpenLayers.Util.alphaHack() ? "image/gif"
+ : "image/png";
+ }
+ }
+
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer
+ *
+ * Returns:
+ * {<OpenLayers.Layer.WMS>} An exact clone of this layer
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.WMS(this.name,
+ this.url,
+ this.params,
+ 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;
+ },
+
+ /**
+ * APIMethod: reverseAxisOrder
+ * Returns true if the axis order is reversed for the WMS version and
+ * projection of the layer.
+ *
+ * Returns:
+ * {Boolean} true if the axis order is reversed, false otherwise.
+ */
+ reverseAxisOrder: function() {
+ var projCode = this.projection.getCode();
+ return parseFloat(this.params.VERSION) >= 1.3 &&
+ !!(this.yx[projCode] || (OpenLayers.Projection.defaults[projCode] &&
+ OpenLayers.Projection.defaults[projCode].yx));
+ },
+
+ /**
+ * Method: getURL
+ * Return a GetMap query string for this layer
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} A bounds representing the bbox for the
+ * request.
+ *
+ * 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 imageSize = this.getImageSize();
+ var newParams = {};
+ // WMS 1.3 introduced axis order
+ var reverseAxisOrder = this.reverseAxisOrder();
+ newParams.BBOX = this.encodeBBOX ?
+ bounds.toBBOX(null, reverseAxisOrder) :
+ bounds.toArray(reverseAxisOrder);
+ newParams.WIDTH = imageSize.w;
+ newParams.HEIGHT = imageSize.h;
+ var requestString = this.getFullRequestString(newParams);
+ return requestString;
+ },
+
+ /**
+ * APIMethod: mergeNewParams
+ * Catch changeParams and uppercase the new params to be merged in
+ * before calling changeParams on the super class.
+ *
+ * Once params have been changed, the tiles will be reloaded with
+ * the new parameters.
+ *
+ * Parameters:
+ * newParams - {Object} Hashtable of new params to use
+ */
+ mergeNewParams:function(newParams) {
+ var upperParams = OpenLayers.Util.upperCaseObject(newParams);
+ var newArguments = [upperParams];
+ return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this,
+ newArguments);
+ },
+
+ /**
+ * APIMethod: getFullRequestString
+ * Combine the layer's url with its params and these newParams.
+ *
+ * Add the SRS parameter from projection -- this is probably
+ * more eloquently done via a setProjection() method, but this
+ * works for now and always.
+ *
+ * Parameters:
+ * newParams - {Object}
+ * altUrl - {String} Use this as the url instead of the layer's url
+ *
+ * Returns:
+ * {String}
+ */
+ getFullRequestString:function(newParams, altUrl) {
+ var mapProjection = this.map.getProjectionObject();
+ var projectionCode = this.projection && this.projection.equals(mapProjection) ?
+ this.projection.getCode() :
+ mapProjection.getCode();
+ var value = (projectionCode == "none") ? null : projectionCode;
+ if (parseFloat(this.params.VERSION) >= 1.3) {
+ this.params.CRS = value;
+ } else {
+ this.params.SRS = value;
+ }
+
+ if (typeof this.params.TRANSPARENT == "boolean") {
+ newParams.TRANSPARENT = this.params.TRANSPARENT ? "TRUE" : "FALSE";
+ }
+
+ return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(
+ this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.WMS"
+});
+/* ======================================================================
+ OpenLayers/Renderer/SVG.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/Elements.js
+ */
+
+/**
+ * Class: OpenLayers.Renderer.SVG
+ *
+ * Inherits:
+ * - <OpenLayers.Renderer.Elements>
+ */
+OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
+
+ /**
+ * Property: xmlns
+ * {String}
+ */
+ xmlns: "http://www.w3.org/2000/svg",
+
+ /**
+ * Property: xlinkns
+ * {String}
+ */
+ xlinkns: "http://www.w3.org/1999/xlink",
+
+ /**
+ * Constant: MAX_PIXEL
+ * {Integer} Firefox has a limitation where values larger or smaller than
+ * about 15000 in an SVG document lock the browser up. This
+ * works around it.
+ */
+ MAX_PIXEL: 15000,
+
+ /**
+ * Property: translationParameters
+ * {Object} Hash with "x" and "y" properties
+ */
+ translationParameters: null,
+
+ /**
+ * Property: symbolMetrics
+ * {Object} Cache for symbol metrics according to their svg coordinate
+ * space. This is an object keyed by the symbol's id, and values are
+ * an array of [width, centerX, centerY].
+ */
+ symbolMetrics: null,
+
+ /**
+ * Constructor: OpenLayers.Renderer.SVG
+ *
+ * Parameters:
+ * containerID - {String}
+ */
+ initialize: function(containerID) {
+ if (!this.supported()) {
+ return;
+ }
+ OpenLayers.Renderer.Elements.prototype.initialize.apply(this,
+ arguments);
+ this.translationParameters = {x: 0, y: 0};
+
+ this.symbolMetrics = {};
+ },
+
+ /**
+ * APIMethod: supported
+ *
+ * Returns:
+ * {Boolean} Whether or not the browser supports the SVG renderer
+ */
+ supported: function() {
+ var svgFeature = "http://www.w3.org/TR/SVG11/feature#";
+ return (document.implementation &&
+ (document.implementation.hasFeature("org.w3c.svg", "1.0") ||
+ document.implementation.hasFeature(svgFeature + "SVG", "1.1") ||
+ document.implementation.hasFeature(svgFeature + "BasicStructure", "1.1") ));
+ },
+
+ /**
+ * Method: inValidRange
+ * See #669 for more information
+ *
+ * Parameters:
+ * x - {Integer}
+ * y - {Integer}
+ * xyOnly - {Boolean} whether or not to just check for x and y, which means
+ * to not take the current translation parameters into account if true.
+ *
+ * Returns:
+ * {Boolean} Whether or not the 'x' and 'y' coordinates are in the
+ * valid range.
+ */
+ inValidRange: function(x, y, xyOnly) {
+ var left = x + (xyOnly ? 0 : this.translationParameters.x);
+ var top = y + (xyOnly ? 0 : this.translationParameters.y);
+ return (left >= -this.MAX_PIXEL && left <= this.MAX_PIXEL &&
+ top >= -this.MAX_PIXEL && top <= this.MAX_PIXEL);
+ },
+
+ /**
+ * Method: setExtent
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>}
+ * 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.Elements.prototype.setExtent.apply(this, arguments);
+
+ var resolution = this.getResolution(),
+ left = -extent.left / resolution,
+ top = extent.top / resolution;
+
+ // If the resolution has changed, start over changing the corner, because
+ // the features will redraw.
+ if (resolutionChanged) {
+ this.left = left;
+ this.top = top;
+ // Set the viewbox
+ var extentString = "0 0 " + this.size.w + " " + this.size.h;
+
+ this.rendererRoot.setAttributeNS(null, "viewBox", extentString);
+ this.translate(this.xOffset, 0);
+ return true;
+ } else {
+ var inRange = this.translate(left - this.left + this.xOffset, top - this.top);
+ if (!inRange) {
+ // recenter the coordinate system
+ this.setExtent(extent, true);
+ }
+ return coordSysUnchanged && inRange;
+ }
+ },
+
+ /**
+ * Method: translate
+ * Transforms the SVG coordinate system
+ *
+ * Parameters:
+ * x - {Float}
+ * y - {Float}
+ *
+ * Returns:
+ * {Boolean} true if the translation parameters are in the valid coordinates
+ * range, false otherwise.
+ */
+ translate: function(x, y) {
+ if (!this.inValidRange(x, y, true)) {
+ return false;
+ } else {
+ var transformString = "";
+ if (x || y) {
+ transformString = "translate(" + x + "," + y + ")";
+ }
+ this.root.setAttributeNS(null, "transform", transformString);
+ this.translationParameters = {x: x, y: y};
+ return true;
+ }
+ },
+
+ /**
+ * Method: setSize
+ * Sets the size of the drawing surface.
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>} The size of the drawing surface
+ */
+ setSize: function(size) {
+ OpenLayers.Renderer.prototype.setSize.apply(this, arguments);
+
+ this.rendererRoot.setAttributeNS(null, "width", this.size.w);
+ this.rendererRoot.setAttributeNS(null, "height", this.size.h);
+ },
+
+ /**
+ * Method: getNodeType
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ *
+ * Returns:
+ * {String} The corresponding node type for the specified geometry
+ */
+ getNodeType: function(geometry, style) {
+ var nodeType = null;
+ switch (geometry.CLASS_NAME) {
+ case "OpenLayers.Geometry.Point":
+ if (style.externalGraphic) {
+ nodeType = "image";
+ } else if (this.isComplexSymbol(style.graphicName)) {
+ nodeType = "svg";
+ } else {
+ nodeType = "circle";
+ }
+ break;
+ case "OpenLayers.Geometry.Rectangle":
+ nodeType = "rect";
+ break;
+ case "OpenLayers.Geometry.LineString":
+ nodeType = "polyline";
+ break;
+ case "OpenLayers.Geometry.LinearRing":
+ nodeType = "polygon";
+ break;
+ case "OpenLayers.Geometry.Polygon":
+ case "OpenLayers.Geometry.Curve":
+ nodeType = "path";
+ break;
+ default:
+ break;
+ }
+ return nodeType;
+ },
+
+ /**
+ * Method: setStyle
+ * Use to set all the style attributes to a SVG node.
+ *
+ * Takes care to adjust stroke width and point radius to be
+ * resolution-relative
+ *
+ * Parameters:
+ * node - {SVGDomElement} An SVG element to decorate
+ * style - {Object}
+ * options - {Object} Currently supported options include
+ * 'isFilled' {Boolean} and
+ * 'isStroked' {Boolean}
+ */
+ setStyle: function(node, style, options) {
+ style = style || node._style;
+ options = options || node._options;
+
+ var title = style.title || style.graphicTitle;
+ if (title) {
+ node.setAttributeNS(null, "title", title);
+ //Standards-conformant SVG
+ // Prevent duplicate nodes. See issue https://github.com/openlayers/openlayers/issues/92
+ var titleNode = node.getElementsByTagName("title");
+ if (titleNode.length > 0) {
+ titleNode[0].firstChild.textContent = title;
+ } else {
+ var label = this.nodeFactory(null, "title");
+ label.textContent = title;
+ node.appendChild(label);
+ }
+ }
+
+ var r = parseFloat(node.getAttributeNS(null, "r"));
+ var widthFactor = 1;
+ var pos;
+ if (node._geometryClass == "OpenLayers.Geometry.Point" && r) {
+ node.style.visibility = "";
+ if (style.graphic === false) {
+ node.style.visibility = "hidden";
+ } else if (style.externalGraphic) {
+ pos = this.getPosition(node);
+ if (style.graphicWidth && style.graphicHeight) {
+ node.setAttributeNS(null, "preserveAspectRatio", "none");
+ }
+ 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;
+
+ node.setAttributeNS(null, "x", (pos.x + xOffset).toFixed());
+ node.setAttributeNS(null, "y", (pos.y + yOffset).toFixed());
+ node.setAttributeNS(null, "width", width);
+ node.setAttributeNS(null, "height", height);
+ node.setAttributeNS(this.xlinkns, "xlink:href", style.externalGraphic);
+ node.setAttributeNS(null, "style", "opacity: "+opacity);
+ node.onclick = OpenLayers.Event.preventDefault;
+ } else if (this.isComplexSymbol(style.graphicName)) {
+ // the symbol viewBox is three times as large as the symbol
+ var offset = style.pointRadius * 3;
+ var size = offset * 2;
+ var src = this.importSymbol(style.graphicName);
+ pos = this.getPosition(node);
+ widthFactor = this.symbolMetrics[src.id][0] * 3 / size;
+
+ // remove the node from the dom before we modify it. This
+ // prevents various rendering issues in Safari and FF
+ var parent = node.parentNode;
+ var nextSibling = node.nextSibling;
+ if(parent) {
+ parent.removeChild(node);
+ }
+
+ // The more appropriate way to implement this would be use/defs,
+ // but due to various issues in several browsers, it is safer to
+ // copy the symbols instead of referencing them.
+ // See e.g. ticket http://trac.osgeo.org/openlayers/ticket/2985
+ // and this email thread
+ // http://osgeo-org.1803224.n2.nabble.com/Select-Control-Ctrl-click-on-Feature-with-a-graphicName-opens-new-browser-window-tc5846039.html
+ node.firstChild && node.removeChild(node.firstChild);
+ node.appendChild(src.firstChild.cloneNode(true));
+ node.setAttributeNS(null, "viewBox", src.getAttributeNS(null, "viewBox"));
+
+ node.setAttributeNS(null, "width", size);
+ node.setAttributeNS(null, "height", size);
+ node.setAttributeNS(null, "x", pos.x - offset);
+ node.setAttributeNS(null, "y", pos.y - offset);
+
+ // now that the node has all its new properties, insert it
+ // back into the dom where it was
+ if(nextSibling) {
+ parent.insertBefore(node, nextSibling);
+ } else if(parent) {
+ parent.appendChild(node);
+ }
+ } else {
+ node.setAttributeNS(null, "r", style.pointRadius);
+ }
+
+ var rotation = style.rotation;
+
+ if ((rotation !== undefined || node._rotation !== undefined) && pos) {
+ node._rotation = rotation;
+ rotation |= 0;
+ if (node.nodeName !== "svg") {
+ node.setAttributeNS(null, "transform",
+ "rotate(" + rotation + " " + pos.x + " " +
+ pos.y + ")");
+ } else {
+ var metrics = this.symbolMetrics[src.id];
+ node.firstChild.setAttributeNS(null, "transform", "rotate("
+ + rotation + " "
+ + metrics[1] + " "
+ + metrics[2] + ")");
+ }
+ }
+ }
+
+ if (options.isFilled) {
+ node.setAttributeNS(null, "fill", style.fillColor);
+ node.setAttributeNS(null, "fill-opacity", style.fillOpacity);
+ } else {
+ node.setAttributeNS(null, "fill", "none");
+ }
+
+ if (options.isStroked) {
+ node.setAttributeNS(null, "stroke", style.strokeColor);
+ node.setAttributeNS(null, "stroke-opacity", style.strokeOpacity);
+ node.setAttributeNS(null, "stroke-width", style.strokeWidth * widthFactor);
+ node.setAttributeNS(null, "stroke-linecap", style.strokeLinecap || "round");
+ // Hard-coded linejoin for now, to make it look the same as in VML.
+ // There is no strokeLinejoin property yet for symbolizers.
+ node.setAttributeNS(null, "stroke-linejoin", "round");
+ style.strokeDashstyle && node.setAttributeNS(null,
+ "stroke-dasharray", this.dashStyle(style, widthFactor));
+ } else {
+ node.setAttributeNS(null, "stroke", "none");
+ }
+
+ if (style.pointerEvents) {
+ node.setAttributeNS(null, "pointer-events", style.pointerEvents);
+ }
+
+ if (style.cursor != null) {
+ node.setAttributeNS(null, "cursor", style.cursor);
+ }
+
+ return node;
+ },
+
+ /**
+ * Method: dashStyle
+ *
+ * Parameters:
+ * style - {Object}
+ * widthFactor - {Number}
+ *
+ * Returns:
+ * {String} A SVG compliant 'stroke-dasharray' value
+ */
+ dashStyle: function(style, widthFactor) {
+ var w = style.strokeWidth * widthFactor;
+ var str = style.strokeDashstyle;
+ switch (str) {
+ case 'solid':
+ return 'none';
+ case 'dot':
+ return [1, 4 * w].join();
+ case 'dash':
+ return [4 * w, 4 * w].join();
+ case 'dashdot':
+ return [4 * w, 4 * w, 1, 4 * w].join();
+ case 'longdash':
+ return [8 * w, 4 * w].join();
+ case 'longdashdot':
+ return [8 * w, 4 * w, 1, 4 * w].join();
+ default:
+ return OpenLayers.String.trim(str).replace(/\s+/g, ",");
+ }
+ },
+
+ /**
+ * Method: createNode
+ *
+ * Parameters:
+ * type - {String} Kind of node to draw
+ * id - {String} Id for node
+ *
+ * Returns:
+ * {DOMElement} A new node of the given type and id
+ */
+ createNode: function(type, id) {
+ var node = document.createElementNS(this.xmlns, type);
+ if (id) {
+ node.setAttributeNS(null, "id", id);
+ }
+ return node;
+ },
+
+ /**
+ * Method: nodeTypeCompare
+ *
+ * Parameters:
+ * node - {SVGDomElement} An SVG element
+ * type - {String} Kind of node
+ *
+ * Returns:
+ * {Boolean} Whether or not the specified node is of the specified type
+ */
+ nodeTypeCompare: function(node, type) {
+ return (type == node.nodeName);
+ },
+
+ /**
+ * Method: createRenderRoot
+ *
+ * Returns:
+ * {DOMElement} The specific render engine's root element
+ */
+ createRenderRoot: function() {
+ var svg = this.nodeFactory(this.container.id + "_svgRoot", "svg");
+ svg.style.display = "block";
+ return svg;
+ },
+
+ /**
+ * Method: createRoot
+ *
+ * Parameters:
+ * suffix - {String} suffix to append to the id
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ createRoot: function(suffix) {
+ return this.nodeFactory(this.container.id + suffix, "g");
+ },
+
+ /**
+ * Method: createDefs
+ *
+ * Returns:
+ * {DOMElement} The element to which we'll add the symbol definitions
+ */
+ createDefs: function() {
+ var defs = this.nodeFactory(this.container.id + "_defs", "defs");
+ this.rendererRoot.appendChild(defs);
+ return defs;
+ },
+
+ /**************************************
+ * *
+ * GEOMETRY DRAWING FUNCTIONS *
+ * *
+ **************************************/
+
+ /**
+ * Method: drawPoint
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the point
+ */
+ drawPoint: function(node, geometry) {
+ return this.drawCircle(node, geometry, 1);
+ },
+
+ /**
+ * Method: drawCircle
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ * radius - {Float}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the circle
+ */
+ drawCircle: function(node, geometry, radius) {
+ var resolution = this.getResolution();
+ var x = ((geometry.x - this.featureDx) / resolution + this.left);
+ var y = (this.top - geometry.y / resolution);
+
+ if (this.inValidRange(x, y)) {
+ node.setAttributeNS(null, "cx", x);
+ node.setAttributeNS(null, "cy", y);
+ node.setAttributeNS(null, "r", radius);
+ return node;
+ } else {
+ return false;
+ }
+
+ },
+
+ /**
+ * Method: drawLineString
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.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) {
+ var componentsResult = this.getComponentsString(geometry.components);
+ if (componentsResult.path) {
+ node.setAttributeNS(null, "points", componentsResult.path);
+ return (componentsResult.complete ? node : null);
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: drawLinearRing
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.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) {
+ var componentsResult = this.getComponentsString(geometry.components);
+ if (componentsResult.path) {
+ node.setAttributeNS(null, "points", componentsResult.path);
+ return (componentsResult.complete ? node : null);
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: drawPolygon
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.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) {
+ var d = "";
+ var draw = true;
+ var complete = true;
+ var linearRingResult, path;
+ for (var j=0, len=geometry.components.length; j<len; j++) {
+ d += " M";
+ linearRingResult = this.getComponentsString(
+ geometry.components[j].components, " ");
+ path = linearRingResult.path;
+ if (path) {
+ d += " " + path;
+ complete = linearRingResult.complete && complete;
+ } else {
+ draw = false;
+ }
+ }
+ d += " z";
+ if (draw) {
+ node.setAttributeNS(null, "d", d);
+ node.setAttributeNS(null, "fill-rule", "evenodd");
+ return complete ? node : null;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: drawRectangle
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the rectangle
+ */
+ drawRectangle: function(node, geometry) {
+ var resolution = this.getResolution();
+ var x = ((geometry.x - this.featureDx) / resolution + this.left);
+ var y = (this.top - geometry.y / resolution);
+
+ if (this.inValidRange(x, y)) {
+ node.setAttributeNS(null, "x", x);
+ node.setAttributeNS(null, "y", y);
+ node.setAttributeNS(null, "width", geometry.width / resolution);
+ node.setAttributeNS(null, "height", geometry.height / resolution);
+ return node;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: drawText
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * featureId - {String}
+ * style -
+ * location - {<OpenLayers.Geometry.Point>}
+ */
+ drawText: function(featureId, style, location) {
+ var drawOutline = (!!style.labelOutlineWidth);
+ // First draw text in halo color and size and overlay the
+ // normal text afterwards
+ if (drawOutline) {
+ var outlineStyle = OpenLayers.Util.extend({}, style);
+ outlineStyle.fontColor = outlineStyle.labelOutlineColor;
+ outlineStyle.fontStrokeColor = outlineStyle.labelOutlineColor;
+ outlineStyle.fontStrokeWidth = style.labelOutlineWidth;
+ if (style.labelOutlineOpacity) {
+ outlineStyle.fontOpacity = style.labelOutlineOpacity;
+ }
+ delete outlineStyle.labelOutlineWidth;
+ this.drawText(featureId, outlineStyle, location);
+ }
+
+ var resolution = this.getResolution();
+
+ var x = ((location.x - this.featureDx) / resolution + this.left);
+ var y = (location.y / resolution - this.top);
+
+ var suffix = (drawOutline)?this.LABEL_OUTLINE_SUFFIX:this.LABEL_ID_SUFFIX;
+ var label = this.nodeFactory(featureId + suffix, "text");
+
+ label.setAttributeNS(null, "x", x);
+ label.setAttributeNS(null, "y", -y);
+
+ if (style.fontColor) {
+ label.setAttributeNS(null, "fill", style.fontColor);
+ }
+ if (style.fontStrokeColor) {
+ label.setAttributeNS(null, "stroke", style.fontStrokeColor);
+ }
+ if (style.fontStrokeWidth) {
+ label.setAttributeNS(null, "stroke-width", style.fontStrokeWidth);
+ }
+ if (style.fontOpacity) {
+ label.setAttributeNS(null, "opacity", style.fontOpacity);
+ }
+ if (style.fontFamily) {
+ label.setAttributeNS(null, "font-family", style.fontFamily);
+ }
+ if (style.fontSize) {
+ label.setAttributeNS(null, "font-size", style.fontSize);
+ }
+ if (style.fontWeight) {
+ label.setAttributeNS(null, "font-weight", style.fontWeight);
+ }
+ if (style.fontStyle) {
+ label.setAttributeNS(null, "font-style", style.fontStyle);
+ }
+ if (style.labelSelect === true) {
+ label.setAttributeNS(null, "pointer-events", "visible");
+ label._featureId = featureId;
+ } else {
+ label.setAttributeNS(null, "pointer-events", "none");
+ }
+ var align = style.labelAlign || OpenLayers.Renderer.defaultSymbolizer.labelAlign;
+ label.setAttributeNS(null, "text-anchor",
+ OpenLayers.Renderer.SVG.LABEL_ALIGN[align[0]] || "middle");
+
+ if (OpenLayers.IS_GECKO === true) {
+ label.setAttributeNS(null, "dominant-baseline",
+ OpenLayers.Renderer.SVG.LABEL_ALIGN[align[1]] || "central");
+ }
+
+ var labelRows = style.label.split('\n');
+ var numRows = labelRows.length;
+ while (label.childNodes.length > numRows) {
+ label.removeChild(label.lastChild);
+ }
+ for (var i = 0; i < numRows; i++) {
+ var tspan = this.nodeFactory(featureId + suffix + "_tspan_" + i, "tspan");
+ if (style.labelSelect === true) {
+ tspan._featureId = featureId;
+ tspan._geometry = location;
+ tspan._geometryClass = location.CLASS_NAME;
+ }
+ if (OpenLayers.IS_GECKO === false) {
+ tspan.setAttributeNS(null, "baseline-shift",
+ OpenLayers.Renderer.SVG.LABEL_VSHIFT[align[1]] || "-35%");
+ }
+ tspan.setAttribute("x", x);
+ if (i == 0) {
+ var vfactor = OpenLayers.Renderer.SVG.LABEL_VFACTOR[align[1]];
+ if (vfactor == null) {
+ vfactor = -.5;
+ }
+ tspan.setAttribute("dy", (vfactor*(numRows-1)) + "em");
+ } else {
+ tspan.setAttribute("dy", "1em");
+ }
+ tspan.textContent = (labelRows[i] === '') ? ' ' : labelRows[i];
+ if (!tspan.parentNode) {
+ label.appendChild(tspan);
+ }
+ }
+
+ if (!label.parentNode) {
+ this.textRoot.appendChild(label);
+ }
+ },
+
+ /**
+ * Method: getComponentString
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry.Point>)} Array of points
+ * separator - {String} character between coordinate pairs. Defaults to ","
+ *
+ * Returns:
+ * {Object} hash with properties "path" (the string created from the
+ * components and "complete" (false if the renderer was unable to
+ * draw all components)
+ */
+ getComponentsString: function(components, separator) {
+ var renderCmp = [];
+ var complete = true;
+ var len = components.length;
+ var strings = [];
+ var str, component;
+ for(var i=0; i<len; i++) {
+ component = components[i];
+ renderCmp.push(component);
+ str = this.getShortString(component);
+ if (str) {
+ strings.push(str);
+ } else {
+ // The current component is outside the valid range. Let's
+ // see if the previous or next component is inside the range.
+ // If so, add the coordinate of the intersection with the
+ // valid range bounds.
+ if (i > 0) {
+ if (this.getShortString(components[i - 1])) {
+ strings.push(this.clipLine(components[i],
+ components[i-1]));
+ }
+ }
+ if (i < len - 1) {
+ if (this.getShortString(components[i + 1])) {
+ strings.push(this.clipLine(components[i],
+ components[i+1]));
+ }
+ }
+ complete = false;
+ }
+ }
+
+ return {
+ path: strings.join(separator || ","),
+ complete: complete
+ };
+ },
+
+ /**
+ * Method: clipLine
+ * Given two points (one inside the valid range, and one outside),
+ * clips the line betweeen the two points so that the new points are both
+ * inside the valid range.
+ *
+ * Parameters:
+ * badComponent - {<OpenLayers.Geometry.Point>} original geometry of the
+ * invalid point
+ * goodComponent - {<OpenLayers.Geometry.Point>} original geometry of the
+ * valid point
+ * Returns
+ * {String} the SVG coordinate pair of the clipped point (like
+ * getShortString), or an empty string if both passed componets are at
+ * the same point.
+ */
+ clipLine: function(badComponent, goodComponent) {
+ if (goodComponent.equals(badComponent)) {
+ return "";
+ }
+ var resolution = this.getResolution();
+ var maxX = this.MAX_PIXEL - this.translationParameters.x;
+ var maxY = this.MAX_PIXEL - this.translationParameters.y;
+ var x1 = (goodComponent.x - this.featureDx) / resolution + this.left;
+ var y1 = this.top - goodComponent.y / resolution;
+ var x2 = (badComponent.x - this.featureDx) / resolution + this.left;
+ var y2 = this.top - badComponent.y / resolution;
+ var k;
+ if (x2 < -maxX || x2 > maxX) {
+ k = (y2 - y1) / (x2 - x1);
+ x2 = x2 < 0 ? -maxX : maxX;
+ y2 = y1 + (x2 - x1) * k;
+ }
+ if (y2 < -maxY || y2 > maxY) {
+ k = (x2 - x1) / (y2 - y1);
+ y2 = y2 < 0 ? -maxY : maxY;
+ x2 = x1 + (y2 - y1) * k;
+ }
+ return x2 + "," + y2;
+ },
+
+ /**
+ * Method: getShortString
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {String} or false if point is outside the valid range
+ */
+ getShortString: function(point) {
+ var resolution = this.getResolution();
+ var x = ((point.x - this.featureDx) / resolution + this.left);
+ var y = (this.top - point.y / resolution);
+
+ if (this.inValidRange(x, y)) {
+ return x + "," + y;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: getPosition
+ * Finds the position of an svg node.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ *
+ * Returns:
+ * {Object} hash with x and y properties, representing the coordinates
+ * within the svg coordinate system
+ */
+ getPosition: function(node) {
+ return({
+ x: parseFloat(node.getAttributeNS(null, "cx")),
+ y: parseFloat(node.getAttributeNS(null, "cy"))
+ });
+ },
+
+ /**
+ * Method: importSymbol
+ * add a new symbol definition from the rendererer's symbol hash
+ *
+ * Parameters:
+ * graphicName - {String} name of the symbol to import
+ *
+ * Returns:
+ * {DOMElement} - the imported symbol
+ */
+ importSymbol: function (graphicName) {
+ if (!this.defs) {
+ // create svg defs tag
+ this.defs = this.createDefs();
+ }
+ var id = this.container.id + "-" + graphicName;
+
+ // check if symbol already exists in the defs
+ var existing = document.getElementById(id);
+ if (existing != null) {
+ return existing;
+ }
+
+ var symbol = OpenLayers.Renderer.symbol[graphicName];
+ if (!symbol) {
+ throw new Error(graphicName + ' is not a valid symbol name');
+ }
+
+ var symbolNode = this.nodeFactory(id, "symbol");
+ var node = this.nodeFactory(null, "polygon");
+ symbolNode.appendChild(node);
+ var symbolExtent = new OpenLayers.Bounds(
+ Number.MAX_VALUE, Number.MAX_VALUE, 0, 0);
+
+ var points = [];
+ var x,y;
+ for (var i=0; i<symbol.length; i=i+2) {
+ x = symbol[i];
+ y = symbol[i+1];
+ symbolExtent.left = Math.min(symbolExtent.left, x);
+ symbolExtent.bottom = Math.min(symbolExtent.bottom, y);
+ symbolExtent.right = Math.max(symbolExtent.right, x);
+ symbolExtent.top = Math.max(symbolExtent.top, y);
+ points.push(x, ",", y);
+ }
+
+ node.setAttributeNS(null, "points", points.join(" "));
+
+ var width = symbolExtent.getWidth();
+ var height = symbolExtent.getHeight();
+ // create a viewBox three times as large as the symbol itself,
+ // to allow for strokeWidth being displayed correctly at the corners.
+ var viewBox = [symbolExtent.left - width,
+ symbolExtent.bottom - height, width * 3, height * 3];
+ symbolNode.setAttributeNS(null, "viewBox", viewBox.join(" "));
+ this.symbolMetrics[id] = [
+ Math.max(width, height),
+ symbolExtent.getCenterLonLat().lon,
+ symbolExtent.getCenterLonLat().lat
+ ];
+
+ this.defs.appendChild(symbolNode);
+ return symbolNode;
+ },
+
+ /**
+ * Method: getFeatureIdFromEvent
+ *
+ * Parameters:
+ * evt - {Object} An <OpenLayers.Event> object
+ *
+ * Returns:
+ * {String} A feature id or undefined.
+ */
+ getFeatureIdFromEvent: function(evt) {
+ var featureId = OpenLayers.Renderer.Elements.prototype.getFeatureIdFromEvent.apply(this, arguments);
+ if(!featureId) {
+ var target = evt.target;
+ featureId = target.parentNode && target != this.rendererRoot ?
+ target.parentNode._featureId : undefined;
+ }
+ return featureId;
+ },
+
+ CLASS_NAME: "OpenLayers.Renderer.SVG"
+});
+
+/**
+ * Constant: OpenLayers.Renderer.SVG.LABEL_ALIGN
+ * {Object}
+ */
+OpenLayers.Renderer.SVG.LABEL_ALIGN = {
+ "l": "start",
+ "r": "end",
+ "b": "bottom",
+ "t": "hanging"
+};
+
+/**
+ * Constant: OpenLayers.Renderer.SVG.LABEL_VSHIFT
+ * {Object}
+ */
+OpenLayers.Renderer.SVG.LABEL_VSHIFT = {
+ // according to
+ // http://www.w3.org/Graphics/SVG/Test/20061213/htmlObjectHarness/full-text-align-02-b.html
+ // a baseline-shift of -70% shifts the text exactly from the
+ // bottom to the top of the baseline, so -35% moves the text to
+ // the center of the baseline.
+ "t": "-70%",
+ "b": "0"
+};
+
+/**
+ * Constant: OpenLayers.Renderer.SVG.LABEL_VFACTOR
+ * {Object}
+ */
+OpenLayers.Renderer.SVG.LABEL_VFACTOR = {
+ "t": 0,
+ "b": -1
+};
+
+/**
+ * Function: OpenLayers.Renderer.SVG.preventDefault
+ * *Deprecated*. Use <OpenLayers.Event.preventDefault> method instead.
+ * Used to prevent default events (especially opening images in a new tab on
+ * ctrl-click) from being executed for externalGraphic symbols
+ */
+OpenLayers.Renderer.SVG.preventDefault = function(e) {
+ OpenLayers.Event.preventDefault(e);
+};
+/* ======================================================================
+ 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
+ * <OpenLayers.Format.JSON> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format>
+ */
+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 <write> 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<this.level; ++i) {
+ pieces.push(this.indent);
+ }
+ }
+ return pieces.join('');
+ },
+
+ /**
+ * Method: writeNewline
+ * Output a string representing a newline if in pretty printing mode.
+ *
+ * Returns:
+ * {String} A string representing a new line.
+ */
+ writeNewline: function() {
+ return (this.pretty) ? this.newline : '';
+ },
+
+ /**
+ * Method: writeSpace
+ * Output a string representing a space if in pretty printing mode.
+ *
+ * Returns:
+ * {String} A space.
+ */
+ writeSpace: function() {
+ return (this.pretty) ? this.space : '';
+ },
+
+ /**
+ * Property: serialize
+ * Object with properties corresponding to the serializable data types.
+ * Property values are functions that do the actual serializing.
+ */
+ serialize: {
+ /**
+ * Method: serialize.object
+ * Transform an object into a JSON string.
+ *
+ * Parameters:
+ * object - {Object} The object to be serialized.
+ *
+ * Returns:
+ * {String} A JSON string representing the object.
+ */
+ 'object': function(object) {
+ // three special objects that we want to treat differently
+ if(object == null) {
+ return "null";
+ }
+ if(object.constructor == Date) {
+ return this.serialize.date.apply(this, [object]);
+ }
+ if(object.constructor == Array) {
+ return this.serialize.array.apply(this, [object]);
+ }
+ var pieces = ['{'];
+ this.level += 1;
+ var key, keyJSON, valueJSON;
+
+ var addComma = false;
+ for(key in object) {
+ if(object.hasOwnProperty(key)) {
+ // recursive calls need to allow for sub-classing
+ keyJSON = OpenLayers.Format.JSON.prototype.write.apply(this,
+ [key, this.pretty]);
+ valueJSON = OpenLayers.Format.JSON.prototype.write.apply(this,
+ [object[key], this.pretty]);
+ if(keyJSON != null && valueJSON != null) {
+ if(addComma) {
+ pieces.push(',');
+ }
+ pieces.push(this.writeNewline(), this.writeIndent(),
+ keyJSON, ':', this.writeSpace(), valueJSON);
+ addComma = true;
+ }
+ }
+ }
+
+ this.level -= 1;
+ pieces.push(this.writeNewline(), this.writeIndent(), '}');
+ return pieces.join('');
+ },
+
+ /**
+ * Method: serialize.array
+ * Transform an array into a JSON string.
+ *
+ * Parameters:
+ * array - {Array} The array to be serialized
+ *
+ * Returns:
+ * {String} A JSON string representing the array.
+ */
+ 'array': function(array) {
+ var json;
+ var pieces = ['['];
+ this.level += 1;
+
+ for(var i=0, len=array.length; i<len; ++i) {
+ // recursive calls need to allow for sub-classing
+ json = OpenLayers.Format.JSON.prototype.write.apply(this,
+ [array[i], this.pretty]);
+ if(json != null) {
+ if(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
+ * <OpenLayers.Format.GeoJSON> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.JSON>
+ */
+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 <OpenLayers.Feature.Vector>. If type is "Geometry", the input json
+ * must represent a single geometry, and the return will be an
+ * <OpenLayers.Geometry>. If type is "Feature", the input json must
+ * represent a single feature, and the return will be an
+ * <OpenLayers.Feature.Vector>.
+ */
+ 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<len; ++i) {
+ try {
+ results.push(this.parseFeature(obj.features[i]));
+ } catch(err) {
+ results = null;
+ OpenLayers.Console.error(err);
+ }
+ }
+ break;
+ default:
+ try {
+ var geom = this.parseGeometry(obj);
+ results.push(new OpenLayers.Feature.Vector(geom));
+ } catch(err) {
+ results = null;
+ OpenLayers.Console.error(err);
+ }
+ }
+ break;
+ }
+ }
+ return results;
+ },
+
+ /**
+ * Method: isValidType
+ * Check if a GeoJSON object is a valid representative of the given type.
+ *
+ * Returns:
+ * {Boolean} The object is valid GeoJSON object of the given type.
+ */
+ isValidType: function(obj, type) {
+ var valid = false;
+ switch(type) {
+ case "Geometry":
+ if(OpenLayers.Util.indexOf(
+ ["Point", "MultiPoint", "LineString", "MultiLineString",
+ "Polygon", "MultiPolygon", "Box", "GeometryCollection"],
+ obj.type) == -1) {
+ // unsupported geometry type
+ OpenLayers.Console.error("Unsupported geometry type: " +
+ obj.type);
+ } else {
+ valid = true;
+ }
+ break;
+ case "FeatureCollection":
+ // allow for any type to be converted to a feature collection
+ valid = true;
+ break;
+ default:
+ // for Feature types must match
+ if(obj.type == type) {
+ valid = true;
+ } else {
+ OpenLayers.Console.error("Cannot convert types from " +
+ obj.type + " to " + type);
+ }
+ }
+ return valid;
+ },
+
+ /**
+ * Method: parseFeature
+ * Convert a feature object from GeoJSON into an
+ * <OpenLayers.Feature.Vector>.
+ *
+ * Parameters:
+ * obj - {Object} An object created from a GeoJSON object
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} 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 <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * obj - {Object} An object created from a GeoJSON object
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} 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<numGeom; ++i) {
+ components[i] = this.parseGeometry.apply(
+ this, [obj.geometries[i]]
+ );
+ }
+ geometry = new OpenLayers.Geometry.Collection(components);
+ collection = true;
+ } else {
+ if(!(OpenLayers.Util.isArray(obj.coordinates))) {
+ throw "Geometry must have coordinates array: " + obj;
+ }
+ if(!this.parseCoords[obj.type.toLowerCase()]) {
+ throw "Unsupported geometry type: " + obj.type;
+ }
+ try {
+ geometry = this.parseCoords[obj.type.toLowerCase()].apply(
+ this, [obj.coordinates]
+ );
+ } catch(err) {
+ // deal with bad coordinates
+ throw err;
+ }
+ }
+ // We don't reproject collections because the children are reprojected
+ // for us when they are created.
+ if (this.internalProjection && this.externalProjection && !collection) {
+ geometry.transform(this.externalProjection,
+ this.internalProjection);
+ }
+ return geometry;
+ },
+
+ /**
+ * Property: parseCoords
+ * Object with properties corresponding to the GeoJSON geometry types.
+ * Property values are functions that do the actual parsing.
+ */
+ parseCoords: {
+ /**
+ * Method: parseCoords.point
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} 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
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "multipoint": function(array) {
+ var points = [];
+ var p = null;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ p = this.parseCoords["point"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ points.push(p);
+ }
+ return new OpenLayers.Geometry.MultiPoint(points);
+ },
+
+ /**
+ * Method: parseCoords.linestring
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "linestring": function(array) {
+ var points = [];
+ var p = null;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ p = this.parseCoords["point"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ points.push(p);
+ }
+ return new OpenLayers.Geometry.LineString(points);
+ },
+
+ /**
+ * Method: parseCoords.multilinestring
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "multilinestring": function(array) {
+ var lines = [];
+ var l = null;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ l = this.parseCoords["linestring"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ lines.push(l);
+ }
+ return new OpenLayers.Geometry.MultiLineString(lines);
+ },
+
+ /**
+ * Method: parseCoords.polygon
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "polygon": function(array) {
+ var rings = [];
+ var r, l;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ l = this.parseCoords["linestring"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ r = new OpenLayers.Geometry.LinearRing(l.components);
+ rings.push(r);
+ }
+ return new OpenLayers.Geometry.Polygon(rings);
+ },
+
+ /**
+ * Method: parseCoords.multipolygon
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "multipolygon": function(array) {
+ var polys = [];
+ var p = null;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ p = this.parseCoords["polygon"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ polys.push(p);
+ }
+ return new OpenLayers.Geometry.MultiPolygon(polys);
+ },
+
+ /**
+ * Method: parseCoords.box
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} 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 <OpenLayers.Feature.Vector>, <OpenLayers.Geometry>,
+ * 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<numFeatures; ++i) {
+ var element = obj[i];
+ if(!element instanceof OpenLayers.Feature.Vector) {
+ var msg = "FeatureCollection only supports collections " +
+ "of features: " + element;
+ throw msg;
+ }
+ geojson.features[i] = this.extract.feature.apply(
+ this, [element]
+ );
+ }
+ } else if (obj.CLASS_NAME.indexOf("OpenLayers.Geometry") == 0) {
+ geojson = this.extract.geometry.apply(this, [obj]);
+ } else if (obj instanceof OpenLayers.Feature.Vector) {
+ geojson = this.extract.feature.apply(this, [obj]);
+ if(obj.layer && obj.layer.projection) {
+ geojson.crs = this.createCRSObject(obj);
+ }
+ }
+ return OpenLayers.Format.JSON.prototype.write.apply(this,
+ [geojson, pretty]);
+ },
+
+ /**
+ * Method: createCRSObject
+ * Create the CRS object for an object.
+ *
+ * Parameters:
+ * object - {<OpenLayers.Feature.Vector>}
+ *
+ * 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 - {<OpenLayers.Feature.Vector>}
+ *
+ * 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 - {<OpenLayers.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 - {<OpenLayers.Geometry.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 - {<OpenLayers.Geometry.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<len; ++i) {
+ array.push(this.extract.point.apply(this, [multipoint.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.linestring
+ * Return an array of coordinate arrays from a linestring.
+ *
+ * Parameters:
+ * linestring - {<OpenLayers.Geometry.LineString>}
+ *
+ * Returns:
+ * {Array} An array of coordinate arrays representing
+ * the linestring.
+ */
+ 'linestring': function(linestring) {
+ var array = [];
+ for(var i=0, len=linestring.components.length; i<len; ++i) {
+ array.push(this.extract.point.apply(this, [linestring.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.multilinestring
+ * Return an array of linestring arrays from a linestring.
+ *
+ * Parameters:
+ * multilinestring - {<OpenLayers.Geometry.MultiLineString>}
+ *
+ * Returns:
+ * {Array} An array of linestring arrays representing
+ * the multilinestring.
+ */
+ 'multilinestring': function(multilinestring) {
+ var array = [];
+ for(var i=0, len=multilinestring.components.length; i<len; ++i) {
+ array.push(this.extract.linestring.apply(this, [multilinestring.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.polygon
+ * Return an array of linear ring arrays from a polygon.
+ *
+ * Parameters:
+ * polygon - {<OpenLayers.Geometry.Polygon>}
+ *
+ * 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<len; ++i) {
+ array.push(this.extract.linestring.apply(this, [polygon.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.multipolygon
+ * Return an array of polygon arrays from a multipolygon.
+ *
+ * Parameters:
+ * multipolygon - {<OpenLayers.Geometry.MultiPolygon>}
+ *
+ * Returns:
+ * {Array} An array of polygon arrays representing
+ * the multipolygon
+ */
+ 'multipolygon': function(multipolygon) {
+ var array = [];
+ for(var i=0, len=multipolygon.components.length; i<len; ++i) {
+ array.push(this.extract.polygon.apply(this, [multipolygon.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.collection
+ * Return an array of geometries from a geometry collection.
+ *
+ * Parameters:
+ * collection - {<OpenLayers.Geometry.Collection>}
+ *
+ * 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<len; ++i) {
+ array[i] = this.extract.geometry.apply(
+ this, [collection.components[i]]
+ );
+ }
+ return array;
+ }
+
+
+ },
+
+ CLASS_NAME: "OpenLayers.Format.GeoJSON"
+
+});
+/* ======================================================================
+ OpenLayers/Control/DrawFeature.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/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Control.DrawFeature
+ * The DrawFeature control draws point, line or polygon features on a vector
+ * layer when active.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.DrawFeature = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>}
+ */
+ layer: null,
+
+ /**
+ * Property: callbacks
+ * {Object} The functions that are sent to the handler for callback
+ */
+ callbacks: null,
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.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 <OpenLayers.Control.events>):
+ * featureadded - Triggered when a feature is added
+ */
+
+ /**
+ * APIProperty: multi
+ * {Boolean} Cast features to multi-part geometries before passing to the
+ * layer. Default is false.
+ */
+ multi: false,
+
+ /**
+ * APIProperty: featureAdded
+ * {Function} Called after each feature is added
+ */
+ featureAdded: function() {},
+
+ /**
+ * APIProperty: handlerOptions
+ * {Object} Used to set non-default properties on the control's handler
+ */
+
+ /**
+ * Constructor: OpenLayers.Control.DrawFeature
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>}
+ * handler - {<OpenLayers.Handler>}
+ * options - {Object}
+ */
+ initialize: function(layer, handler, options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ this.callbacks = OpenLayers.Util.extend(
+ {
+ done: this.drawFeature,
+ modify: function(vertex, feature) {
+ this.layer.events.triggerEvent(
+ "sketchmodified", {vertex: vertex, feature: feature}
+ );
+ },
+ create: function(vertex, feature) {
+ this.layer.events.triggerEvent(
+ "sketchstarted", {vertex: vertex, feature: feature}
+ );
+ }
+ },
+ this.callbacks
+ );
+ this.layer = layer;
+ this.handlerOptions = this.handlerOptions || {};
+ this.handlerOptions.layerOptions = OpenLayers.Util.applyDefaults(
+ this.handlerOptions.layerOptions, {
+ renderers: layer.renderers, rendererOptions: layer.rendererOptions
+ }
+ );
+ if (!("multi" in this.handlerOptions)) {
+ this.handlerOptions.multi = this.multi;
+ }
+ var sketchStyle = this.layer.styleMap && this.layer.styleMap.styles.temporary;
+ if(sketchStyle) {
+ this.handlerOptions.layerOptions = OpenLayers.Util.applyDefaults(
+ this.handlerOptions.layerOptions,
+ {styleMap: new OpenLayers.StyleMap({"default": sketchStyle})}
+ );
+ }
+ this.handler = new handler(this, this.callbacks, this.handlerOptions);
+ },
+
+ /**
+ * Method: drawFeature
+ */
+ drawFeature: function(geometry) {
+ var feature = new OpenLayers.Feature.Vector(geometry);
+ var proceed = this.layer.events.triggerEvent(
+ "sketchcomplete", {feature: feature}
+ );
+ if(proceed !== false) {
+ feature.state = OpenLayers.State.INSERT;
+ this.layer.addFeatures([feature]);
+ this.featureAdded(feature);
+ this.events.triggerEvent("featureadded",{feature : feature});
+ }
+ },
+
+ /**
+ * APIMethod: insertXY
+ * Insert a point in the current sketch given x & y coordinates.
+ *
+ * Parameters:
+ * x - {Number} The x-coordinate of the point.
+ * y - {Number} The y-coordinate of the point.
+ */
+ insertXY: function(x, y) {
+ if (this.handler && this.handler.line) {
+ this.handler.insertXY(x, y);
+ }
+ },
+
+ /**
+ * APIMethod: insertDeltaXY
+ * Insert a point given offsets from the previously inserted point.
+ *
+ * Parameters:
+ * dx - {Number} The x-coordinate offset of the point.
+ * dy - {Number} The y-coordinate offset of the point.
+ */
+ insertDeltaXY: function(dx, dy) {
+ if (this.handler && this.handler.line) {
+ this.handler.insertDeltaXY(dx, dy);
+ }
+ },
+
+ /**
+ * APIMethod: insertDirectionLength
+ * Insert a point in the current sketch given a direction and a length.
+ *
+ * Parameters:
+ * direction - {Number} Degrees clockwise from the positive x-axis.
+ * length - {Number} Distance from the previously drawn point.
+ */
+ insertDirectionLength: function(direction, length) {
+ if (this.handler && this.handler.line) {
+ this.handler.insertDirectionLength(direction, length);
+ }
+ },
+
+ /**
+ * APIMethod: insertDeflectionLength
+ * Insert a point in the current sketch given a deflection and a length.
+ * The deflection should be degrees clockwise from the previously
+ * digitized segment.
+ *
+ * Parameters:
+ * deflection - {Number} Degrees clockwise from the previous segment.
+ * length - {Number} Distance from the previously drawn point.
+ */
+ insertDeflectionLength: function(deflection, length) {
+ if (this.handler && this.handler.line) {
+ this.handler.insertDeflectionLength(deflection, length);
+ }
+ },
+
+ /**
+ * APIMethod: undo
+ * Remove the most recently added point in the current sketch geometry.
+ *
+ * Returns:
+ * {Boolean} An edit was undone.
+ */
+ undo: function() {
+ return this.handler.undo && this.handler.undo();
+ },
+
+ /**
+ * APIMethod: redo
+ * Reinsert the most recently removed point resulting from an <undo> call.
+ * The undo stack is deleted whenever a point is added by other means.
+ *
+ * Returns:
+ * {Boolean} An edit was redone.
+ */
+ redo: function() {
+ return this.handler.redo && this.handler.redo();
+ },
+
+ /**
+ * APIMethod: finishSketch
+ * Finishes the sketch without including the currently drawn point.
+ * This method can be called to terminate drawing programmatically
+ * instead of waiting for the user to end the sketch.
+ */
+ finishSketch: function() {
+ this.handler.finishGeometry();
+ },
+
+ /**
+ * APIMethod: cancel
+ * Cancel the current sketch. This removes the current sketch and keeps
+ * the drawing control active.
+ */
+ cancel: function() {
+ this.handler.cancel();
+ },
+
+ CLASS_NAME: "OpenLayers.Control.DrawFeature"
+});
+/* ======================================================================
+ OpenLayers/Handler/Pinch.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.Pinch
+ * The pinch handler is used to deal with sequences of browser events related
+ * to pinch gestures. The handler is used by controls that want to know
+ * when a pinch sequence begins, when a pinch is happening, and when it has
+ * finished.
+ *
+ * Controls that use the pinch handler typically construct it with callbacks
+ * for 'start', 'move', and 'done'. Callbacks for these keys are
+ * called when the pinch begins, with each change, and when the pinch is
+ * done.
+ *
+ * Create a new pinch handler with the <OpenLayers.Handler.Pinch> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Pinch = OpenLayers.Class(OpenLayers.Handler, {
+
+ /**
+ * Property: started
+ * {Boolean} When a touchstart event is received, we want to record it,
+ * but not set 'pinching' until the touchmove get started after
+ * starting.
+ */
+ started: false,
+
+ /**
+ * Property: stopDown
+ * {Boolean} Stop propagation of touchstart events from getting to
+ * listeners on the same element. Default is false.
+ */
+ stopDown: false,
+
+ /**
+ * Property: pinching
+ * {Boolean}
+ */
+ pinching: false,
+
+ /**
+ * Property: last
+ * {Object} Object that store informations related to pinch last touch.
+ */
+ last: null,
+
+ /**
+ * Property: start
+ * {Object} Object that store informations related to pinch touchstart.
+ */
+ start: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Pinch
+ * Returns OpenLayers.Handler.Pinch
+ *
+ * Parameters:
+ * control - {<OpenLayers.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 functions to be called when
+ * the pinch operation start, change, or is finished. The callbacks
+ * should expect to receive an object argument, which contains
+ * information about scale, distance, and position of touch points.
+ * options - {Object}
+ */
+
+ /**
+ * Method: touchstart
+ * Handle touchstart events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchstart: function(evt) {
+ var propagate = true;
+ this.pinching = false;
+ if (OpenLayers.Event.isMultiTouch(evt)) {
+ this.started = true;
+ this.last = this.start = {
+ distance: this.getDistance(evt.touches),
+ delta: 0,
+ scale: 1
+ };
+ this.callback("start", [evt, this.start]);
+ propagate = !this.stopDown;
+ } else if (this.started) {
+ // Some webkit versions send fake single-touch events during
+ // multitouch, which cause the drag handler to trigger
+ return false;
+ } else {
+ this.started = false;
+ this.start = null;
+ this.last = null;
+ }
+ // prevent document dragging
+ OpenLayers.Event.preventDefault(evt);
+ return propagate;
+ },
+
+ /**
+ * Method: touchmove
+ * Handle touchmove events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchmove: function(evt) {
+ if (this.started && OpenLayers.Event.isMultiTouch(evt)) {
+ this.pinching = true;
+ var current = this.getPinchData(evt);
+ this.callback("move", [evt, current]);
+ this.last = current;
+ // prevent document dragging
+ OpenLayers.Event.stop(evt);
+ } else if (this.started) {
+ // Some webkit versions send fake single-touch events during
+ // multitouch, which cause the drag handler to trigger
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * Method: touchend
+ * Handle touchend events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchend: function(evt) {
+ if (this.started && !OpenLayers.Event.isMultiTouch(evt)) {
+ this.started = false;
+ this.pinching = false;
+ this.callback("done", [evt, this.start, this.last]);
+ this.start = null;
+ this.last = null;
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * 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.pinching = 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.pinching = false;
+ this.start = null;
+ this.last = null;
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: getDistance
+ * Get the distance in pixels between two touches.
+ *
+ * Parameters:
+ * touches - {Array(Object)}
+ *
+ * Returns:
+ * {Number} The distance in pixels.
+ */
+ getDistance: function(touches) {
+ var t0 = touches[0];
+ var t1 = touches[1];
+ return Math.sqrt(
+ Math.pow(t0.olClientX - t1.olClientX, 2) +
+ Math.pow(t0.olClientY - t1.olClientY, 2)
+ );
+ },
+
+
+ /**
+ * Method: getPinchData
+ * Get informations about the pinch event.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Object} Object that contains data about the current pinch.
+ */
+ getPinchData: function(evt) {
+ var distance = this.getDistance(evt.touches);
+ var scale = distance / this.start.distance;
+ return {
+ distance: distance,
+ delta: this.last.distance - distance,
+ scale: scale
+ };
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Pinch"
+});
+
+/* ======================================================================
+ OpenLayers/Handler/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/Handler/Path.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Polygon
+ * Handler to draw a polygon on the map. Polygon is displayed on mouse down,
+ * moves on mouse move, and is finished on mouse up.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler.Path>
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Polygon = OpenLayers.Class(OpenLayers.Handler.Path, {
+
+ /**
+ * APIProperty: holeModifier
+ * {String} Key modifier to trigger hole digitizing. Acceptable values are
+ * "altKey", "shiftKey", or "ctrlKey". If not set, no hole digitizing
+ * will take place. Default is null.
+ */
+ holeModifier: null,
+
+ /**
+ * Property: drawingHole
+ * {Boolean} Currently drawing an interior ring.
+ */
+ drawingHole: false,
+
+ /**
+ * Property: polygon
+ * {<OpenLayers.Feature.Vector>}
+ */
+ polygon: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Polygon
+ * Create a Polygon Handler.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that owns this handler
+ * callbacks - {Object} An object with a properties whose values are
+ * functions. Various callbacks described below.
+ * options - {Object} An optional object with properties to be set on the
+ * handler
+ *
+ * Named callbacks:
+ * create - Called when a sketch is first created. Callback called with
+ * the creation point geometry and sketch feature.
+ * modify - Called with each move of a vertex with the vertex (point)
+ * geometry and the sketch feature.
+ * point - Called as each point is added. Receives the new point geometry.
+ * done - Called when the point drawing is finished. The callback will
+ * recieve a single argument, the polygon geometry.
+ * cancel - Called when the handler is deactivated while drawing. The
+ * cancel callback will receive a geometry.
+ */
+
+ /**
+ * Method: createFeature
+ * Add temporary geometries
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} The initial pixel location for the new
+ * feature.
+ */
+ createFeature: function(pixel) {
+ var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
+ var geometry = new OpenLayers.Geometry.Point(
+ lonlat.lon, lonlat.lat
+ );
+ this.point = new OpenLayers.Feature.Vector(geometry);
+ this.line = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.LinearRing([this.point.geometry])
+ );
+ this.polygon = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Polygon([this.line.geometry])
+ );
+ this.callback("create", [this.point.geometry, this.getSketch()]);
+ this.point.geometry.clearBounds();
+ this.layer.addFeatures([this.polygon, this.point], {silent: true});
+ },
+
+ /**
+ * Method: addPoint
+ * Add point to geometry.
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} The pixel location for the new point.
+ */
+ addPoint: function(pixel) {
+ if(!this.drawingHole && this.holeModifier &&
+ this.evt && this.evt[this.holeModifier]) {
+ var geometry = this.point.geometry;
+ var features = this.control.layer.features;
+ var candidate, polygon;
+ // look for intersections, last drawn gets priority
+ for (var i=features.length-1; i>=0; --i) {
+ candidate = features[i].geometry;
+ if ((candidate instanceof OpenLayers.Geometry.Polygon ||
+ candidate instanceof OpenLayers.Geometry.MultiPolygon) &&
+ candidate.intersects(geometry)) {
+ polygon = features[i];
+ this.control.layer.removeFeatures([polygon], {silent: true});
+ this.control.layer.events.registerPriority(
+ "sketchcomplete", this, this.finalizeInteriorRing
+ );
+ this.control.layer.events.registerPriority(
+ "sketchmodified", this, this.enforceTopology
+ );
+ polygon.geometry.addComponent(this.line.geometry);
+ this.polygon = polygon;
+ this.drawingHole = true;
+ break;
+ }
+ }
+ }
+ OpenLayers.Handler.Path.prototype.addPoint.apply(this, arguments);
+ },
+
+ /**
+ * Method: getCurrentPointIndex
+ *
+ * Returns:
+ * {Number} The index of the most recently drawn point.
+ */
+ getCurrentPointIndex: function() {
+ return this.line.geometry.components.length - 2;
+ },
+
+ /**
+ * Method: enforceTopology
+ * Simple topology enforcement for drawing interior rings. Ensures vertices
+ * of interior rings are contained by exterior ring. Other topology
+ * rules are enforced in <finalizeInteriorRing> to allow drawing of
+ * rings that intersect only during the sketch (e.g. a "C" shaped ring
+ * that nearly encloses another ring).
+ */
+ enforceTopology: function(event) {
+ var point = event.vertex;
+ var components = this.line.geometry.components;
+ // ensure that vertices of interior ring are contained by exterior ring
+ if (!this.polygon.geometry.intersects(point)) {
+ var last = components[components.length-3];
+ point.x = last.x;
+ point.y = last.y;
+ }
+ },
+
+ /**
+ * Method: finishGeometry
+ * Finish the geometry and send it back to the control.
+ */
+ finishGeometry: function() {
+ var index = this.line.geometry.components.length - 2;
+ this.line.geometry.removeComponent(this.line.geometry.components[index]);
+ this.removePoint();
+ this.finalize();
+ },
+
+ /**
+ * Method: finalizeInteriorRing
+ * Enforces that new ring has some area and doesn't contain vertices of any
+ * other rings.
+ */
+ finalizeInteriorRing: function() {
+ var ring = this.line.geometry;
+ // ensure that ring has some area
+ var modified = (ring.getArea() !== 0);
+ if (modified) {
+ // ensure that new ring doesn't intersect any other rings
+ var rings = this.polygon.geometry.components;
+ for (var i=rings.length-2; i>=0; --i) {
+ if (ring.intersects(rings[i])) {
+ modified = false;
+ break;
+ }
+ }
+ if (modified) {
+ // ensure that new ring doesn't contain any other rings
+ var target;
+ outer: for (var i=rings.length-2; i>0; --i) {
+ var points = rings[i].components;
+ for (var j=0, jj=points.length; j<jj; ++j) {
+ if (ring.containsPoint(points[j])) {
+ modified = false;
+ break outer;
+ }
+ }
+ }
+ }
+ }
+ if (modified) {
+ if (this.polygon.state !== OpenLayers.State.INSERT) {
+ this.polygon.state = OpenLayers.State.UPDATE;
+ }
+ } else {
+ this.polygon.geometry.removeComponent(ring);
+ }
+ this.restoreFeature();
+ return false;
+ },
+
+ /**
+ * APIMethod: cancel
+ * Finish the geometry and call the "cancel" callback.
+ */
+ cancel: function() {
+ if (this.drawingHole) {
+ this.polygon.geometry.removeComponent(this.line.geometry);
+ this.restoreFeature(true);
+ }
+ return OpenLayers.Handler.Path.prototype.cancel.apply(this, arguments);
+ },
+
+ /**
+ * Method: restoreFeature
+ * Move the feature from the sketch layer to the target layer.
+ *
+ * Properties:
+ * cancel - {Boolean} Cancel drawing. If falsey, the "sketchcomplete" event
+ * will be fired.
+ */
+ restoreFeature: function(cancel) {
+ this.control.layer.events.unregister(
+ "sketchcomplete", this, this.finalizeInteriorRing
+ );
+ this.control.layer.events.unregister(
+ "sketchmodified", this, this.enforceTopology
+ );
+ this.layer.removeFeatures([this.polygon], {silent: true});
+ this.control.layer.addFeatures([this.polygon], {silent: true});
+ this.drawingHole = false;
+ if (!cancel) {
+ // Re-trigger "sketchcomplete" so other listeners can do their
+ // business. While this is somewhat sloppy (if a listener is
+ // registered with registerPriority - not common - between the start
+ // and end of a single ring drawing - very uncommon - it will be
+ // called twice).
+ // TODO: In 3.0, collapse sketch handlers into geometry specific
+ // drawing controls.
+ this.control.layer.events.triggerEvent(
+ "sketchcomplete", {feature : this.polygon}
+ );
+ }
+ },
+
+ /**
+ * Method: destroyFeature
+ * Destroy temporary geometries
+ *
+ * Parameters:
+ * force - {Boolean} Destroy even if persist is true.
+ */
+ destroyFeature: function(force) {
+ OpenLayers.Handler.Path.prototype.destroyFeature.call(
+ this, force);
+ this.polygon = null;
+ },
+
+ /**
+ * Method: drawFeature
+ * Render geometries on the temporary layer.
+ */
+ drawFeature: function() {
+ this.layer.drawFeature(this.polygon, this.style);
+ this.layer.drawFeature(this.point, this.style);
+ },
+
+ /**
+ * Method: getSketch
+ * Return the sketch feature.
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>}
+ */
+ getSketch: function() {
+ return this.polygon;
+ },
+
+ /**
+ * Method: getGeometry
+ * Return the sketch geometry. If <multi> is true, this will return
+ * a multi-part geometry.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Polygon>}
+ */
+ getGeometry: function() {
+ var geometry = this.polygon && this.polygon.geometry;
+ if(geometry && this.multi) {
+ geometry = new OpenLayers.Geometry.MultiPolygon([geometry]);
+ }
+ return geometry;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Polygon"
+});
+/* ======================================================================
+ OpenLayers/Control/Geolocate.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/Geometry/Point.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Geolocate
+ * The Geolocate control wraps w3c geolocation API into control that can be
+ * bound to a map, and generate events on location update
+ *
+ * To use this control requires to load the proj4js library if the projection
+ * of the map is not EPSG:4326 or EPSG:900913.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Geolocate = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.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 <OpenLayers.Control.events>):
+ * locationupdated - Triggered when browser return a new position. Listeners will
+ * receive an object with a 'position' property which is the browser.geolocation.position
+ * native object, as well as a 'point' property which is the location transformed in the
+ * current map projection.
+ * locationfailed - Triggered when geolocation has failed
+ * locationuncapable - Triggered when control is activated on a browser
+ * which doesn't support geolocation
+ */
+
+ /**
+ * Property: geolocation
+ * {Object} The geolocation engine, as a property to be possibly mocked.
+ * This is set lazily to avoid a memory leak in IE9.
+ */
+ geolocation: null,
+
+ /**
+ * Property: available
+ * {Boolean} The navigator.geolocation object is available.
+ */
+ available: ('geolocation' in navigator),
+
+ /**
+ * APIProperty: bind
+ * {Boolean} If true, map center will be set on location update.
+ */
+ bind: true,
+
+ /**
+ * APIProperty: watch
+ * {Boolean} If true, position will be update regularly.
+ */
+ watch: false,
+
+ /**
+ * APIProperty: geolocationOptions
+ * {Object} Options to pass to the navigator's geolocation API. See
+ * <http://dev.w3.org/geo/api/spec-source.html>. No specific
+ * option is passed to the geolocation API by default.
+ */
+ geolocationOptions: null,
+
+ /**
+ * Constructor: OpenLayers.Control.Geolocate
+ * Create a new control to deal with browser geolocation API
+ *
+ */
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ this.deactivate();
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: activate
+ * Activates the control.
+ *
+ * Returns:
+ * {Boolean} The control was effectively activated.
+ */
+ activate: function () {
+ if (this.available && !this.geolocation) {
+ // set lazily to avoid IE9 memory leak
+ this.geolocation = navigator.geolocation;
+ }
+ if (!this.geolocation) {
+ this.events.triggerEvent("locationuncapable");
+ return false;
+ }
+ if (OpenLayers.Control.prototype.activate.apply(this, arguments)) {
+ if (this.watch) {
+ this.watchId = this.geolocation.watchPosition(
+ OpenLayers.Function.bind(this.geolocate, this),
+ OpenLayers.Function.bind(this.failure, this),
+ this.geolocationOptions
+ );
+ } else {
+ this.getCurrentLocation();
+ }
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivates the control.
+ *
+ * Returns:
+ * {Boolean} The control was effectively deactivated.
+ */
+ deactivate: function () {
+ if (this.active && this.watchId !== null) {
+ this.geolocation.clearWatch(this.watchId);
+ }
+ return OpenLayers.Control.prototype.deactivate.apply(
+ this, arguments
+ );
+ },
+
+ /**
+ * Method: geolocate
+ * Activates the control.
+ *
+ */
+ geolocate: function (position) {
+ var center = new OpenLayers.LonLat(
+ position.coords.longitude,
+ position.coords.latitude
+ ).transform(
+ new OpenLayers.Projection("EPSG:4326"),
+ this.map.getProjectionObject()
+ );
+ if (this.bind) {
+ this.map.setCenter(center);
+ }
+ this.events.triggerEvent("locationupdated", {
+ position: position,
+ point: new OpenLayers.Geometry.Point(
+ center.lon, center.lat
+ )
+ });
+ },
+
+ /**
+ * APIMethod: getCurrentLocation
+ *
+ * Returns:
+ * {Boolean} Returns true if a event will be fired (successfull
+ * registration)
+ */
+ getCurrentLocation: function() {
+ if (!this.active || this.watch) {
+ return false;
+ }
+ this.geolocation.getCurrentPosition(
+ OpenLayers.Function.bind(this.geolocate, this),
+ OpenLayers.Function.bind(this.failure, this),
+ this.geolocationOptions
+ );
+ return true;
+ },
+
+ /**
+ * Method: failure
+ * method called on browser's geolocation failure
+ *
+ */
+ failure: function (error) {
+ this.events.triggerEvent("locationfailed", {error: error});
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Geolocate"
+});
+/* ======================================================================
+ OpenLayers/Protocol/HTTP.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
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+/**
+ * if application uses the query string, for example, for BBOX parameters,
+ * OpenLayers/Format/QueryStringFilter.js should be included in the build config file
+ */
+
+/**
+ * Class: OpenLayers.Protocol.HTTP
+ * A basic HTTP protocol for vector layers. Create a new instance with the
+ * <OpenLayers.Protocol.HTTP> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Protocol>
+ */
+OpenLayers.Protocol.HTTP = OpenLayers.Class(OpenLayers.Protocol, {
+
+ /**
+ * Property: url
+ * {String} Service URL, read-only, set through the options
+ * passed to constructor.
+ */
+ url: null,
+
+ /**
+ * Property: headers
+ * {Object} HTTP request headers, read-only, set through the options
+ * passed to the constructor,
+ * Example: {'Content-Type': 'plain/text'}
+ */
+ headers: null,
+
+ /**
+ * Property: params
+ * {Object} Parameters of GET requests, read-only, set through the options
+ * passed to the constructor,
+ * Example: {'bbox': '5,5,5,5'}
+ */
+ params: null,
+
+ /**
+ * Property: callback
+ * {Object} Function to be called when the <read>, <create>,
+ * <update>, <delete> or <commit> operation completes, read-only,
+ * set through the options passed to the constructor.
+ */
+ callback: null,
+
+ /**
+ * Property: scope
+ * {Object} Callback execution scope, read-only, set through the
+ * options passed to the constructor.
+ */
+ scope: null,
+
+ /**
+ * APIProperty: readWithPOST
+ * {Boolean} true if read operations are done with POST requests
+ * instead of GET, defaults to false.
+ */
+ readWithPOST: false,
+
+ /**
+ * APIProperty: updateWithPOST
+ * {Boolean} true if update operations are done with POST requests
+ * defaults to false.
+ */
+ updateWithPOST: false,
+
+ /**
+ * APIProperty: deleteWithPOST
+ * {Boolean} true if delete operations are done with POST requests
+ * defaults to false.
+ * if true, POST data is set to output of format.write().
+ */
+ deleteWithPOST: false,
+
+ /**
+ * Property: wildcarded.
+ * {Boolean} If true percent signs are added around values
+ * read from LIKE filters, for example if the protocol
+ * read method is passed a LIKE filter whose property
+ * is "foo" and whose value is "bar" the string
+ * "foo__ilike=%bar%" will be sent in the query string;
+ * defaults to false.
+ */
+ wildcarded: false,
+
+ /**
+ * APIProperty: srsInBBOX
+ * {Boolean} Include the SRS identifier in BBOX query string parameter.
+ * 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.HTTP
+ * A class for giving layers generic HTTP protocol.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options include:
+ * url - {String}
+ * headers - {Object}
+ * params - {Object} URL parameters for GET requests
+ * format - {<OpenLayers.Format>}
+ * callback - {Function}
+ * scope - {Object}
+ */
+ initialize: function(options) {
+ options = options || {};
+ this.params = {};
+ this.headers = {};
+ OpenLayers.Protocol.prototype.initialize.apply(this, arguments);
+
+ if (!this.filterToParams && OpenLayers.Format.QueryStringFilter) {
+ var format = new OpenLayers.Format.QueryStringFilter({
+ wildcarded: this.wildcarded,
+ srsInBBOX: this.srsInBBOX
+ });
+ this.filterToParams = function(filter, params) {
+ return format.write(filter, params);
+ };
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the protocol.
+ */
+ destroy: function() {
+ this.params = null;
+ this.headers = null;
+ OpenLayers.Protocol.prototype.destroy.apply(this);
+ },
+
+ /**
+ * APIMethod: filterToParams
+ * Optional method to translate an <OpenLayers.Filter> object into an object
+ * that can be serialized as request query string provided. If a custom
+ * method is not provided, the filter will be serialized using the
+ * <OpenLayers.Format.QueryStringFilter> class.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>} filter to convert.
+ * params - {Object} The parameters object.
+ *
+ * Returns:
+ * {Object} The resulting parameters object.
+ */
+
+ /**
+ * 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.
+ * headers - {Object} Headers to be set on the request.
+ * filter - {<OpenLayers.Filter>} Filter to get serialized as a
+ * query string.
+ * readWithPOST - {Boolean} If the request should be done with POST.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} A response object, whose "priv" property
+ * references the HTTP request, 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 = options || {};
+ options.params = OpenLayers.Util.applyDefaults(
+ options.params, this.options.params);
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+ if (options.filter && this.filterToParams) {
+ options.params = this.filterToParams(
+ options.filter, options.params
+ );
+ }
+ var readWithPOST = (options.readWithPOST !== undefined) ?
+ options.readWithPOST : this.readWithPOST;
+ var resp = new OpenLayers.Protocol.Response({requestType: "read"});
+ if(readWithPOST) {
+ var headers = options.headers || {};
+ headers["Content-Type"] = "application/x-www-form-urlencoded";
+ resp.priv = OpenLayers.Request.POST({
+ url: options.url,
+ callback: this.createCallback(this.handleRead, resp, options),
+ data: OpenLayers.Util.getParameterString(options.params),
+ headers: headers
+ });
+ } else {
+ resp.priv = OpenLayers.Request.GET({
+ url: options.url,
+ callback: this.createCallback(this.handleRead, resp, options),
+ params: options.params,
+ headers: options.headers
+ });
+ }
+ return resp;
+ },
+
+ /**
+ * Method: handleRead
+ * Individual callbacks are created for read, create and update, should
+ * a subclass need to override each one separately.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * the user callback.
+ * options - {Object} The user options passed to the read call.
+ */
+ handleRead: function(resp, options) {
+ this.handleResponse(resp, options);
+ },
+
+ /**
+ * APIMethod: create
+ * Construct a request for writing newly created features.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, whose "priv" property references the HTTP request, this
+ * object is also passed to the callback function when the request
+ * completes, its "features" property is then populated with the
+ * the features received from the server.
+ */
+ create: function(features, options) {
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+
+ var resp = new OpenLayers.Protocol.Response({
+ reqFeatures: features,
+ requestType: "create"
+ });
+
+ resp.priv = OpenLayers.Request.POST({
+ url: options.url,
+ callback: this.createCallback(this.handleCreate, resp, options),
+ headers: options.headers,
+ data: this.format.write(features)
+ });
+
+ return resp;
+ },
+
+ /**
+ * Method: handleCreate
+ * Called the the request issued by <create> is complete. May be overridden
+ * by subclasses.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * any user callback.
+ * options - {Object} The user options passed to the create call.
+ */
+ handleCreate: function(resp, options) {
+ this.handleResponse(resp, options);
+ },
+
+ /**
+ * APIMethod: update
+ * Construct a request updating modified feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, whose "priv" property references the HTTP request, this
+ * object is also passed to the callback function when the request
+ * completes, its "features" property is then populated with the
+ * the feature received from the server.
+ */
+ update: function(feature, options) {
+ options = options || {};
+ var url = options.url ||
+ feature.url ||
+ this.options.url + "/" + feature.fid;
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+
+ var resp = new OpenLayers.Protocol.Response({
+ reqFeatures: feature,
+ requestType: "update"
+ });
+
+ var method = this.updateWithPOST ? "POST" : "PUT";
+ resp.priv = OpenLayers.Request[method]({
+ url: url,
+ callback: this.createCallback(this.handleUpdate, resp, options),
+ headers: options.headers,
+ data: this.format.write(feature)
+ });
+
+ return resp;
+ },
+
+ /**
+ * Method: handleUpdate
+ * Called the the request issued by <update> is complete. May be overridden
+ * by subclasses.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * any user callback.
+ * options - {Object} The user options passed to the update call.
+ */
+ handleUpdate: function(resp, options) {
+ this.handleResponse(resp, options);
+ },
+
+ /**
+ * APIMethod: delete
+ * Construct a request deleting a removed feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, whose "priv" property references the HTTP request, this
+ * object is also passed to the callback function when the request
+ * completes.
+ */
+ "delete": function(feature, options) {
+ options = options || {};
+ var url = options.url ||
+ feature.url ||
+ this.options.url + "/" + feature.fid;
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+
+ var resp = new OpenLayers.Protocol.Response({
+ reqFeatures: feature,
+ requestType: "delete"
+ });
+
+ var method = this.deleteWithPOST ? "POST" : "DELETE";
+ var requestOptions = {
+ url: url,
+ callback: this.createCallback(this.handleDelete, resp, options),
+ headers: options.headers
+ };
+ if (this.deleteWithPOST) {
+ requestOptions.data = this.format.write(feature);
+ }
+ resp.priv = OpenLayers.Request[method](requestOptions);
+
+ return resp;
+ },
+
+ /**
+ * Method: handleDelete
+ * Called the the request issued by <delete> is complete. May be overridden
+ * by subclasses.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * any user callback.
+ * options - {Object} The user options passed to the delete call.
+ */
+ handleDelete: function(resp, options) {
+ this.handleResponse(resp, options);
+ },
+
+ /**
+ * Method: handleResponse
+ * Called by CRUD specific handlers.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.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(resp, options) {
+ var request = resp.priv;
+ if(options.callback) {
+ if(request.status >= 200 && request.status < 300) {
+ // success
+ if(resp.requestType != "delete") {
+ resp.features = this.parseFeatures(request);
+ }
+ resp.code = OpenLayers.Protocol.Response.SUCCESS;
+ } else {
+ // failure
+ resp.code = OpenLayers.Protocol.Response.FAILURE;
+ }
+ options.callback.call(options.scope, resp);
+ }
+ },
+
+ /**
+ * Method: parseFeatures
+ * Read HTTP response body and return features.
+ *
+ * Parameters:
+ * request - {XMLHttpRequest} The request object
+ *
+ * Returns:
+ * {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>} Array of features or a single feature.
+ */
+ parseFeatures: function(request) {
+ var doc = request.responseXML;
+ if (!doc || !doc.documentElement) {
+ doc = request.responseText;
+ }
+ if (!doc || doc.length <= 0) {
+ return null;
+ }
+ return this.format.read(doc);
+ },
+
+ /**
+ * APIMethod: commit
+ * Iterate over each feature and take action based on the feature state.
+ * Possible actions are create, update and delete.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})}
+ * options - {Object} Optional object for setting up intermediate commit
+ * callbacks.
+ *
+ * Valid options:
+ * create - {Object} Optional object to be passed to the <create> method.
+ * update - {Object} Optional object to be passed to the <update> method.
+ * delete - {Object} Optional object to be passed to the <delete> method.
+ * callback - {Function} Optional function to be called when the commit
+ * is complete.
+ * scope - {Object} Optional object to be set as the scope of the callback.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Protocol.Response>)} An array of response objects,
+ * one per request made to the server, each object's "priv" property
+ * references the corresponding HTTP request.
+ */
+ commit: function(features, options) {
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+ var resp = [], nResponses = 0;
+
+ // Divide up features before issuing any requests. This properly
+ // counts requests in the event that any responses come in before
+ // all requests have been issued.
+ var types = {};
+ types[OpenLayers.State.INSERT] = [];
+ types[OpenLayers.State.UPDATE] = [];
+ types[OpenLayers.State.DELETE] = [];
+ var feature, list, requestFeatures = [];
+ for(var i=0, len=features.length; i<len; ++i) {
+ feature = features[i];
+ list = types[feature.state];
+ if(list) {
+ list.push(feature);
+ requestFeatures.push(feature);
+ }
+ }
+ // tally up number of requests
+ var nRequests = (types[OpenLayers.State.INSERT].length > 0 ? 1 : 0) +
+ types[OpenLayers.State.UPDATE].length +
+ types[OpenLayers.State.DELETE].length;
+
+ // This response will be sent to the final callback after all the others
+ // have been fired.
+ var success = true;
+ var finalResponse = new OpenLayers.Protocol.Response({
+ reqFeatures: requestFeatures
+ });
+
+ function insertCallback(response) {
+ var len = response.features ? response.features.length : 0;
+ var fids = new Array(len);
+ for(var i=0; i<len; ++i) {
+ fids[i] = response.features[i].fid;
+ }
+ finalResponse.insertIds = fids;
+ callback.apply(this, [response]);
+ }
+
+ function callback(response) {
+ this.callUserCallback(response, options);
+ success = success && response.success();
+ nResponses++;
+ if (nResponses >= nRequests) {
+ if (options.callback) {
+ finalResponse.code = success ?
+ OpenLayers.Protocol.Response.SUCCESS :
+ OpenLayers.Protocol.Response.FAILURE;
+ options.callback.apply(options.scope, [finalResponse]);
+ }
+ }
+ }
+
+ // start issuing requests
+ var queue = types[OpenLayers.State.INSERT];
+ if(queue.length > 0) {
+ resp.push(this.create(
+ queue, OpenLayers.Util.applyDefaults(
+ {callback: insertCallback, scope: this}, options.create
+ )
+ ));
+ }
+ queue = types[OpenLayers.State.UPDATE];
+ for(var i=queue.length-1; i>=0; --i) {
+ resp.push(this.update(
+ queue[i], OpenLayers.Util.applyDefaults(
+ {callback: callback, scope: this}, options.update
+ ))
+ );
+ }
+ queue = types[OpenLayers.State.DELETE];
+ for(var i=queue.length-1; i>=0; --i) {
+ resp.push(this["delete"](
+ queue[i], OpenLayers.Util.applyDefaults(
+ {callback: callback, scope: this}, options["delete"]
+ ))
+ );
+ }
+ return resp;
+ },
+
+ /**
+ * APIMethod: abort
+ * Abort an ongoing request, the response object passed to
+ * this method must come from this HTTP protocol (as a result
+ * of a create, read, update, delete or commit operation).
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>}
+ */
+ abort: function(response) {
+ if (response) {
+ response.priv.abort();
+ }
+ },
+
+ /**
+ * Method: callUserCallback
+ * This method is used from within the commit method each time an
+ * an HTTP response is received from the server, it is responsible
+ * for calling the user-supplied callbacks.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>}
+ * options - {Object} The map of options passed to the commit call.
+ */
+ callUserCallback: function(resp, options) {
+ var opt = options[resp.requestType];
+ if(opt && opt.callback) {
+ opt.callback.call(opt.scope, resp);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol.HTTP"
+});
+/* ======================================================================
+ OpenLayers/Control/DragPan.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/Handler/Drag.js
+ */
+
+/**
+ * Class: OpenLayers.Control.DragPan
+ * The DragPan control pans the map with a drag of the mouse.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.DragPan = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: type
+ * {OpenLayers.Control.TYPES}
+ */
+ type: OpenLayers.Control.TYPE_TOOL,
+
+ /**
+ * Property: panned
+ * {Boolean} The map moved.
+ */
+ panned: false,
+
+ /**
+ * Property: interval
+ * {Integer} The number of milliseconds that should ellapse before
+ * panning the map again. Defaults to 0 milliseconds, which means that
+ * no separate cycle is used for panning. In most cases you won't want
+ * to change this value. For slow machines/devices larger values can be
+ * tried out.
+ */
+ interval: 0,
+
+ /**
+ * APIProperty: documentDrag
+ * {Boolean} If set to true, mouse dragging will continue even if the
+ * mouse cursor leaves the map viewport. Default is false.
+ */
+ documentDrag: false,
+
+ /**
+ * Property: kinetic
+ * {<OpenLayers.Kinetic>} The OpenLayers.Kinetic object.
+ */
+ kinetic: null,
+
+ /**
+ * APIProperty: enableKinetic
+ * {Boolean} Set this option to enable "kinetic dragging". Can be
+ * set to true or to an object. If set to an object this
+ * object will be passed to the {<OpenLayers.Kinetic>}
+ * constructor. Defaults to true.
+ * To get kinetic dragging, ensure that OpenLayers/Kinetic.js is
+ * included in your build config.
+ */
+ enableKinetic: true,
+
+ /**
+ * APIProperty: kineticInterval
+ * {Integer} Interval in milliseconds between 2 steps in the "kinetic
+ * scrolling". Applies only if enableKinetic is set. Defaults
+ * to 10 milliseconds.
+ */
+ kineticInterval: 10,
+
+
+ /**
+ * Method: draw
+ * Creates a Drag handler, using <panMap> and
+ * <panMapDone> as callbacks.
+ */
+ draw: function() {
+ if (this.enableKinetic && OpenLayers.Kinetic) {
+ var config = {interval: this.kineticInterval};
+ if(typeof this.enableKinetic === "object") {
+ config = OpenLayers.Util.extend(config, this.enableKinetic);
+ }
+ this.kinetic = new OpenLayers.Kinetic(config);
+ }
+ this.handler = new OpenLayers.Handler.Drag(this, {
+ "move": this.panMap,
+ "done": this.panMapDone,
+ "down": this.panMapStart
+ }, {
+ interval: this.interval,
+ documentDrag: this.documentDrag
+ }
+ );
+ },
+
+ /**
+ * Method: panMapStart
+ */
+ panMapStart: function() {
+ if(this.kinetic) {
+ this.kinetic.begin();
+ }
+ },
+
+ /**
+ * Method: panMap
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} Pixel of the mouse position
+ */
+ panMap: function(xy) {
+ if(this.kinetic) {
+ this.kinetic.update(xy);
+ }
+ this.panned = true;
+ this.map.pan(
+ this.handler.last.x - xy.x,
+ this.handler.last.y - xy.y,
+ {dragging: true, animate: false}
+ );
+ },
+
+ /**
+ * Method: panMapDone
+ * Finish the panning operation. Only call setCenter (through <panMap>)
+ * if the map has actually been moved.
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} Pixel of the mouse position
+ */
+ panMapDone: function(xy) {
+ if(this.panned) {
+ var res = null;
+ if (this.kinetic) {
+ res = this.kinetic.end(xy);
+ }
+ this.map.pan(
+ this.handler.last.x - xy.x,
+ this.handler.last.y - xy.y,
+ {dragging: !!res, animate: false}
+ );
+ if (res) {
+ var self = this;
+ this.kinetic.move(res, function(x, y, end) {
+ self.map.pan(x, y, {dragging: !end, animate: false});
+ });
+ }
+ this.panned = false;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.DragPan"
+});
+/* ======================================================================
+ OpenLayers/Control/PinchZoom.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/Pinch.js
+ */
+
+/**
+ * Class: OpenLayers.Control.PinchZoom
+ *
+ * Inherits:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.PinchZoom = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: type
+ * {OpenLayers.Control.TYPES}
+ */
+ type: OpenLayers.Control.TYPE_TOOL,
+
+ /**
+ * Property: pinchOrigin
+ * {Object} Cached object representing the pinch start (in pixels).
+ */
+ pinchOrigin: null,
+
+ /**
+ * Property: currentCenter
+ * {Object} Cached object representing the latest pinch center (in pixels).
+ */
+ currentCenter: null,
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * APIProperty: preserveCenter
+ * {Boolean} Set this to true if you don't want the map center to change
+ * while pinching. For example you may want to set preserveCenter to
+ * true when the user location is being watched and you want to preserve
+ * the user location at the center of the map even if he zooms in or
+ * out using pinch. This property's value can be changed any time on an
+ * existing instance. Default is false.
+ */
+ preserveCenter: false,
+
+ /**
+ * APIProperty: handlerOptions
+ * {Object} Used to set non-default properties on the pinch handler
+ */
+
+ /**
+ * Constructor: OpenLayers.Control.PinchZoom
+ * Create a control for zooming with pinch gestures. This works on devices
+ * with multi-touch support.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * the control
+ */
+ initialize: function(options) {
+ OpenLayers.Control.prototype.initialize.apply(this, arguments);
+ this.handler = new OpenLayers.Handler.Pinch(this, {
+ start: this.pinchStart,
+ move: this.pinchMove,
+ done: this.pinchDone
+ }, this.handlerOptions);
+ },
+
+ /**
+ * Method: pinchStart
+ *
+ * Parameters:
+ * evt - {Event}
+ * pinchData - {Object} pinch data object related to the current touchmove
+ * of the pinch gesture. This give us the current scale of the pinch.
+ */
+ pinchStart: function(evt, pinchData) {
+ var xy = (this.preserveCenter) ?
+ this.map.getPixelFromLonLat(this.map.getCenter()) : evt.xy;
+ this.pinchOrigin = xy;
+ this.currentCenter = xy;
+ },
+
+ /**
+ * Method: pinchMove
+ *
+ * Parameters:
+ * evt - {Event}
+ * pinchData - {Object} pinch data object related to the current touchmove
+ * of the pinch gesture. This give us the current scale of the pinch.
+ */
+ pinchMove: function(evt, pinchData) {
+ var scale = pinchData.scale;
+ var containerOrigin = this.map.layerContainerOriginPx;
+ var pinchOrigin = this.pinchOrigin;
+ var current = (this.preserveCenter) ?
+ this.map.getPixelFromLonLat(this.map.getCenter()) : evt.xy;
+
+ var dx = Math.round((containerOrigin.x + current.x - pinchOrigin.x) + (scale - 1) * (containerOrigin.x - pinchOrigin.x));
+ var dy = Math.round((containerOrigin.y + current.y - pinchOrigin.y) + (scale - 1) * (containerOrigin.y - pinchOrigin.y));
+
+ this.map.applyTransform(dx, dy, scale);
+ this.currentCenter = current;
+ },
+
+ /**
+ * Method: pinchDone
+ *
+ * Parameters:
+ * evt - {Event}
+ * start - {Object} pinch data object related to the touchstart event that
+ * started the pinch gesture.
+ * last - {Object} pinch data object related to the last touchmove event
+ * of the pinch gesture. This give us the final scale of the pinch.
+ */
+ pinchDone: function(evt, start, last) {
+ this.map.applyTransform();
+ var zoom = this.map.getZoomForResolution(this.map.getResolution() / last.scale, true);
+ if (zoom !== this.map.getZoom() || !this.currentCenter.equals(this.pinchOrigin)) {
+ var resolution = this.map.getResolutionForZoom(zoom);
+
+ var location = this.map.getLonLatFromPixel(this.pinchOrigin);
+ var zoomPixel = this.currentCenter;
+ var size = this.map.getSize();
+
+ location.lon += resolution * ((size.w / 2) - zoomPixel.x);
+ location.lat -= resolution * ((size.h / 2) - zoomPixel.y);
+
+ // Force a reflow before calling setCenter. This is to work
+ // around an issue occuring in iOS.
+ //
+ // See https://github.com/openlayers/openlayers/pull/351.
+ //
+ // Without a reflow setting the layer container div's top left
+ // style properties to "0px" - as done in Map.moveTo when zoom
+ // is changed - won't actually correctly reposition the layer
+ // container div.
+ //
+ // Also, we need to use a statement that the Google Closure
+ // compiler won't optimize away.
+ this.map.div.clientWidth = this.map.div.clientWidth;
+
+ this.map.setCenter(location, zoom);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.PinchZoom"
+
+});
+/* ======================================================================
+ 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 <pixelTolerance>, controls can also ignore clicks
+ * that include a drag. Create a new instance with the
+ * <OpenLayers.Handler.Click> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+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 <delayedCall>.
+ */
+ 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
+ * <delayedEvent>.
+ */
+ rightclickTimerId: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Click
+ * Create a new click handler.
+ *
+ * Parameters:
+ * control - {<OpenLayers.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 <rightclickTimerId> 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 <pixelTolerance> 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<ii; ++i) {
+ if (this.getTouchDistance(
+ this.down.touches[i],
+ this.last.touches[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 <timerId> 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 <timerId> 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<len; i++) {
+ touch = evt.touches[i];
+ touches[i] = {
+ clientX: touch.olClientX,
+ clientY: touch.olClientY
+ };
+ }
+ }
+ return {
+ xy: evt.xy,
+ touches: touches
+ };
+ },
+
+ /**
+ * APIMethod: 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.clearTimer();
+ this.down = null;
+ this.first = null;
+ this.last = null;
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Click"
+});
+/* ======================================================================
+ OpenLayers/Control/TouchNavigation.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/DragPan.js
+ * @requires OpenLayers/Control/PinchZoom.js
+ * @requires OpenLayers/Handler/Click.js
+ */
+
+/**
+ * Class: OpenLayers.Control.TouchNavigation
+ * The navigation control handles map browsing with touch events (dragging,
+ * double-tapping, tap with two fingers, and pinch zoom). Create a new
+ * control with the <OpenLayers.Control.TouchNavigation> constructor.
+ *
+ * If you’re only targeting touch enabled devices with your mapping application,
+ * you can create a map with only a TouchNavigation control. The
+ * <OpenLayers.Control.Navigation> control is mobile ready by default, but
+ * you can generate a smaller build of the library by only including this
+ * touch navigation control if you aren't concerned about mouse interaction.
+ *
+ * Inherits:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.TouchNavigation = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: dragPan
+ * {<OpenLayers.Control.DragPan>}
+ */
+ dragPan: null,
+
+ /**
+ * APIProperty: dragPanOptions
+ * {Object} Options passed to the DragPan control.
+ */
+ dragPanOptions: null,
+
+ /**
+ * Property: pinchZoom
+ * {<OpenLayers.Control.PinchZoom>}
+ */
+ pinchZoom: null,
+
+ /**
+ * APIProperty: pinchZoomOptions
+ * {Object} Options passed to the PinchZoom control.
+ */
+ pinchZoomOptions: null,
+
+ /**
+ * APIProperty: clickHandlerOptions
+ * {Object} Options passed to the Click handler.
+ */
+ clickHandlerOptions: null,
+
+ /**
+ * APIProperty: documentDrag
+ * {Boolean} Allow panning of the map by dragging outside map viewport.
+ * Default is false.
+ */
+ documentDrag: false,
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * Constructor: OpenLayers.Control.TouchNavigation
+ * Create a new navigation control
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * the control
+ */
+ initialize: function(options) {
+ this.handlers = {};
+ OpenLayers.Control.prototype.initialize.apply(this, arguments);
+ },
+
+ /**
+ * 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() {
+ this.deactivate();
+ if(this.dragPan) {
+ this.dragPan.destroy();
+ }
+ this.dragPan = null;
+ if (this.pinchZoom) {
+ this.pinchZoom.destroy();
+ delete this.pinchZoom;
+ }
+ OpenLayers.Control.prototype.destroy.apply(this,arguments);
+ },
+
+ /**
+ * Method: activate
+ */
+ activate: function() {
+ if(OpenLayers.Control.prototype.activate.apply(this,arguments)) {
+ this.dragPan.activate();
+ this.handlers.click.activate();
+ this.pinchZoom.activate();
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Method: deactivate
+ */
+ deactivate: function() {
+ if(OpenLayers.Control.prototype.deactivate.apply(this,arguments)) {
+ this.dragPan.deactivate();
+ this.handlers.click.deactivate();
+ this.pinchZoom.deactivate();
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Method: draw
+ */
+ draw: function() {
+ var clickCallbacks = {
+ click: this.defaultClick,
+ dblclick: this.defaultDblClick
+ };
+ var clickOptions = OpenLayers.Util.extend({
+ "double": true,
+ stopDouble: true,
+ pixelTolerance: 2
+ }, this.clickHandlerOptions);
+ this.handlers.click = new OpenLayers.Handler.Click(
+ this, clickCallbacks, clickOptions
+ );
+ this.dragPan = new OpenLayers.Control.DragPan(
+ OpenLayers.Util.extend({
+ map: this.map,
+ documentDrag: this.documentDrag
+ }, this.dragPanOptions)
+ );
+ this.dragPan.draw();
+ this.pinchZoom = new OpenLayers.Control.PinchZoom(
+ OpenLayers.Util.extend({map: this.map}, this.pinchZoomOptions)
+ );
+ },
+
+ /**
+ * Method: defaultClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultClick: function (evt) {
+ if(evt.lastTouches && evt.lastTouches.length == 2) {
+ this.map.zoomOut();
+ }
+ },
+
+ /**
+ * Method: defaultDblClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultDblClick: function (evt) {
+ this.map.zoomTo(this.map.zoom + 1, evt.xy);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.TouchNavigation"
+});
+/* ======================================================================
+ OpenLayers/Protocol/WFS/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/Protocol/WFS/v1.js
+ * @requires OpenLayers/Format/WFST/v1_0_0.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol.WFS.v1_0_0
+ * A WFS v1.0.0 protocol for vector layers. Create a new instance with the
+ * <OpenLayers.Protocol.WFS.v1_0_0> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Protocol.WFS.v1>
+ */
+OpenLayers.Protocol.WFS.v1_0_0 = OpenLayers.Class(OpenLayers.Protocol.WFS.v1, {
+
+ /**
+ * Property: version
+ * {String} WFS version number.
+ */
+ version: "1.0.0",
+
+ /**
+ * Constructor: OpenLayers.Protocol.WFS.v1_0_0
+ * A class for giving layers WFS v1.0.0 protocol.
+ *
+ * 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'.
+ */
+
+ CLASS_NAME: "OpenLayers.Protocol.WFS.v1_0_0"
+});
+/* ======================================================================
+ OpenLayers/TileManager.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
+ * @requires OpenLayers/BaseTypes/Element.js
+ * @requires OpenLayers/Layer/Grid.js
+ * @requires OpenLayers/Tile/Image.js
+ */
+
+/**
+ * Class: OpenLayers.TileManager
+ * Provides queueing of image requests and caching of image elements.
+ *
+ * Queueing avoids unnecessary image requests while changing zoom levels
+ * quickly, and helps improve dragging performance on mobile devices that show
+ * a lag in dragging when loading of new images starts. <zoomDelay> and
+ * <moveDelay> are the configuration options to control this behavior.
+ *
+ * Caching avoids setting the src on image elements for images that have already
+ * been used. Several maps can share a TileManager instance, in which case each
+ * map gets its own tile queue, but all maps share the same tile cache.
+ */
+OpenLayers.TileManager = OpenLayers.Class({
+
+ /**
+ * APIProperty: cacheSize
+ * {Number} Number of image elements to keep referenced in this instance's
+ * cache for fast reuse. Default is 256.
+ */
+ cacheSize: 256,
+
+ /**
+ * APIProperty: tilesPerFrame
+ * {Number} Number of queued tiles to load per frame (see <frameDelay>).
+ * Default is 2.
+ */
+ tilesPerFrame: 2,
+
+ /**
+ * APIProperty: frameDelay
+ * {Number} Delay between tile loading frames (see <tilesPerFrame>) in
+ * milliseconds. Default is 16.
+ */
+ frameDelay: 16,
+
+ /**
+ * APIProperty: moveDelay
+ * {Number} Delay in milliseconds after a map's move event before loading
+ * tiles. Default is 100.
+ */
+ moveDelay: 100,
+
+ /**
+ * APIProperty: zoomDelay
+ * {Number} Delay in milliseconds after a map's zoomend event before loading
+ * tiles. Default is 200.
+ */
+ zoomDelay: 200,
+
+ /**
+ * Property: maps
+ * {Array(<OpenLayers.Map>)} The maps to manage tiles on.
+ */
+ maps: null,
+
+ /**
+ * Property: tileQueueId
+ * {Object} The ids of the <drawTilesFromQueue> loop, keyed by map id.
+ */
+ tileQueueId: null,
+
+ /**
+ * Property: tileQueue
+ * {Object(Array(<OpenLayers.Tile>))} Tiles queued for drawing, keyed by
+ * map id.
+ */
+ tileQueue: null,
+
+ /**
+ * Property: tileCache
+ * {Object} Cached image elements, keyed by URL.
+ */
+ tileCache: null,
+
+ /**
+ * Property: tileCacheIndex
+ * {Array(String)} URLs of cached tiles. First entry is the least recently
+ * used.
+ */
+ tileCacheIndex: null,
+
+ /**
+ * Constructor: OpenLayers.TileManager
+ * Constructor for a new <OpenLayers.TileManager> instance.
+ *
+ * Parameters:
+ * options - {Object} Configuration for this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ this.maps = [];
+ this.tileQueueId = {};
+ this.tileQueue = {};
+ this.tileCache = {};
+ this.tileCacheIndex = [];
+ },
+
+ /**
+ * Method: addMap
+ * Binds this instance to a map
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ addMap: function(map) {
+ if (this._destroyed || !OpenLayers.Layer.Grid) {
+ return;
+ }
+ this.maps.push(map);
+ this.tileQueue[map.id] = [];
+ for (var i=0, ii=map.layers.length; i<ii; ++i) {
+ this.addLayer({layer: map.layers[i]});
+ }
+ map.events.on({
+ move: this.move,
+ zoomend: this.zoomEnd,
+ changelayer: this.changeLayer,
+ addlayer: this.addLayer,
+ preremovelayer: this.removeLayer,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: removeMap
+ * Unbinds this instance from a map
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ if (this._destroyed || !OpenLayers.Layer.Grid) {
+ return;
+ }
+ window.clearTimeout(this.tileQueueId[map.id]);
+ if (map.layers) {
+ for (var i=0, ii=map.layers.length; i<ii; ++i) {
+ this.removeLayer({layer: map.layers[i]});
+ }
+ }
+ if (map.events) {
+ map.events.un({
+ move: this.move,
+ zoomend: this.zoomEnd,
+ changelayer: this.changeLayer,
+ addlayer: this.addLayer,
+ preremovelayer: this.removeLayer,
+ scope: this
+ });
+ }
+ delete this.tileQueue[map.id];
+ delete this.tileQueueId[map.id];
+ OpenLayers.Util.removeItem(this.maps, map);
+ },
+
+ /**
+ * Method: move
+ * Handles the map's move event
+ *
+ * Parameters:
+ * evt - {Object} Listener argument
+ */
+ move: function(evt) {
+ this.updateTimeout(evt.object, this.moveDelay, true);
+ },
+
+ /**
+ * Method: zoomEnd
+ * Handles the map's zoomEnd event
+ *
+ * Parameters:
+ * evt - {Object} Listener argument
+ */
+ zoomEnd: function(evt) {
+ this.updateTimeout(evt.object, this.zoomDelay);
+ },
+
+ /**
+ * Method: changeLayer
+ * Handles the map's changeLayer event
+ *
+ * Parameters:
+ * evt - {Object} Listener argument
+ */
+ changeLayer: function(evt) {
+ if (evt.property === 'visibility' || evt.property === 'params') {
+ this.updateTimeout(evt.object, 0);
+ }
+ },
+
+ /**
+ * Method: addLayer
+ * Handles the map's addlayer event
+ *
+ * Parameters:
+ * evt - {Object} The listener argument
+ */
+ addLayer: function(evt) {
+ var layer = evt.layer;
+ if (layer instanceof OpenLayers.Layer.Grid) {
+ layer.events.on({
+ addtile: this.addTile,
+ retile: this.clearTileQueue,
+ scope: this
+ });
+ var i, j, tile;
+ for (i=layer.grid.length-1; i>=0; --i) {
+ for (j=layer.grid[i].length-1; j>=0; --j) {
+ tile = layer.grid[i][j];
+ this.addTile({tile: tile});
+ if (tile.url && !tile.imgDiv) {
+ this.manageTileCache({object: tile});
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: removeLayer
+ * Handles the map's preremovelayer event
+ *
+ * Parameters:
+ * evt - {Object} The listener argument
+ */
+ removeLayer: function(evt) {
+ var layer = evt.layer;
+ if (layer instanceof OpenLayers.Layer.Grid) {
+ this.clearTileQueue({object: layer});
+ if (layer.events) {
+ layer.events.un({
+ addtile: this.addTile,
+ retile: this.clearTileQueue,
+ scope: this
+ });
+ }
+ if (layer.grid) {
+ var i, j, tile;
+ for (i=layer.grid.length-1; i>=0; --i) {
+ for (j=layer.grid[i].length-1; j>=0; --j) {
+ tile = layer.grid[i][j];
+ this.unloadTile({object: tile});
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: updateTimeout
+ * Applies the <moveDelay> or <zoomDelay> to the <drawTilesFromQueue> loop,
+ * and schedules more queue processing after <frameDelay> if there are still
+ * tiles in the queue.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>} The map to update the timeout for
+ * delay - {Number} The delay to apply
+ * nice - {Boolean} If true, the timeout function will only be created if
+ * the tilequeue is not empty. This is used by the move handler to
+ * avoid impacts on dragging performance. For other events, the tile
+ * queue may not be populated yet, so we need to set the timer
+ * regardless of the queue size.
+ */
+ updateTimeout: function(map, delay, nice) {
+ window.clearTimeout(this.tileQueueId[map.id]);
+ var tileQueue = this.tileQueue[map.id];
+ if (!nice || tileQueue.length) {
+ this.tileQueueId[map.id] = window.setTimeout(
+ OpenLayers.Function.bind(function() {
+ this.drawTilesFromQueue(map);
+ if (tileQueue.length) {
+ this.updateTimeout(map, this.frameDelay);
+ }
+ }, this), delay
+ );
+ }
+ },
+
+ /**
+ * Method: addTile
+ * Listener for the layer's addtile event
+ *
+ * Parameters:
+ * evt - {Object} The listener argument
+ */
+ addTile: function(evt) {
+ if (evt.tile instanceof OpenLayers.Tile.Image) {
+ evt.tile.events.on({
+ beforedraw: this.queueTileDraw,
+ beforeload: this.manageTileCache,
+ loadend: this.addToCache,
+ unload: this.unloadTile,
+ scope: this
+ });
+ } else {
+ // Layer has the wrong tile type, so don't handle it any longer
+ this.removeLayer({layer: evt.tile.layer});
+ }
+ },
+
+ /**
+ * Method: unloadTile
+ * Listener for the tile's unload event
+ *
+ * Parameters:
+ * evt - {Object} The listener argument
+ */
+ unloadTile: function(evt) {
+ var tile = evt.object;
+ tile.events.un({
+ beforedraw: this.queueTileDraw,
+ beforeload: this.manageTileCache,
+ loadend: this.addToCache,
+ unload: this.unloadTile,
+ scope: this
+ });
+ OpenLayers.Util.removeItem(this.tileQueue[tile.layer.map.id], tile);
+ },
+
+ /**
+ * Method: queueTileDraw
+ * Adds a tile to the queue that will draw it.
+ *
+ * Parameters:
+ * evt - {Object} Listener argument of the tile's beforedraw event
+ */
+ queueTileDraw: function(evt) {
+ var tile = evt.object;
+ var queued = false;
+ var layer = tile.layer;
+ var url = layer.getURL(tile.bounds);
+ var img = this.tileCache[url];
+ if (img && img.className !== 'olTileImage') {
+ // cached image no longer valid, e.g. because we're olTileReplacing
+ delete this.tileCache[url];
+ OpenLayers.Util.removeItem(this.tileCacheIndex, url);
+ img = null;
+ }
+ // queue only if image with same url not cached already
+ if (layer.url && (layer.async || !img)) {
+ // add to queue only if not in queue already
+ var tileQueue = this.tileQueue[layer.map.id];
+ if (!~OpenLayers.Util.indexOf(tileQueue, tile)) {
+ tileQueue.push(tile);
+ }
+ queued = true;
+ }
+ return !queued;
+ },
+
+ /**
+ * Method: drawTilesFromQueue
+ * Draws tiles from the tileQueue, and unqueues the tiles
+ */
+ drawTilesFromQueue: function(map) {
+ var tileQueue = this.tileQueue[map.id];
+ var limit = this.tilesPerFrame;
+ var animating = map.zoomTween && map.zoomTween.playing;
+ while (!animating && tileQueue.length && limit) {
+ tileQueue.shift().draw(true);
+ --limit;
+ }
+ },
+
+ /**
+ * Method: manageTileCache
+ * Adds, updates, removes and fetches cache entries.
+ *
+ * Parameters:
+ * evt - {Object} Listener argument of the tile's beforeload event
+ */
+ manageTileCache: function(evt) {
+ var tile = evt.object;
+ var img = this.tileCache[tile.url];
+ if (img) {
+ // if image is on its layer's backbuffer, remove it from backbuffer
+ if (img.parentNode &&
+ OpenLayers.Element.hasClass(img.parentNode, 'olBackBuffer')) {
+ img.parentNode.removeChild(img);
+ img.id = null;
+ }
+ // only use image from cache if it is not on a layer already
+ if (!img.parentNode) {
+ img.style.visibility = 'hidden';
+ img.style.opacity = 0;
+ tile.setImage(img);
+ // LRU - move tile to the end of the array to mark it as the most
+ // recently used
+ OpenLayers.Util.removeItem(this.tileCacheIndex, tile.url);
+ this.tileCacheIndex.push(tile.url);
+ }
+ }
+ },
+
+ /**
+ * Method: addToCache
+ *
+ * Parameters:
+ * evt - {Object} Listener argument for the tile's loadend event
+ */
+ addToCache: function(evt) {
+ var tile = evt.object;
+ if (!this.tileCache[tile.url]) {
+ if (!OpenLayers.Element.hasClass(tile.imgDiv, 'olImageLoadError')) {
+ if (this.tileCacheIndex.length >= this.cacheSize) {
+ delete this.tileCache[this.tileCacheIndex[0]];
+ this.tileCacheIndex.shift();
+ }
+ this.tileCache[tile.url] = tile.imgDiv;
+ this.tileCacheIndex.push(tile.url);
+ }
+ }
+ },
+
+ /**
+ * Method: clearTileQueue
+ * Clears the tile queue from tiles of a specific layer
+ *
+ * Parameters:
+ * evt - {Object} Listener argument of the layer's retile event
+ */
+ clearTileQueue: function(evt) {
+ var layer = evt.object;
+ var tileQueue = this.tileQueue[layer.map.id];
+ for (var i=tileQueue.length-1; i>=0; --i) {
+ if (tileQueue[i].layer === layer) {
+ tileQueue.splice(i, 1);
+ }
+ }
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ for (var i=this.maps.length-1; i>=0; --i) {
+ this.removeMap(this.maps[i]);
+ }
+ this.maps = null;
+ this.tileQueue = null;
+ this.tileQueueId = null;
+ this.tileCache = null;
+ this.tileCacheIndex = null;
+ this._destroyed = true;
+ }
+
+});