/* OpenLayers.js -- OpenLayers Map Viewer Library Copyright (c) 2006-2013 by OpenLayers Contributors Published under the 2-clause BSD license. See http://openlayers.org/dev/license.txt for the full text of the license, and http://openlayers.org/dev/authors.txt for full list of contributors. Includes compressed code under the following licenses: (For uncompressed versions of the code used, please see the OpenLayers Github repository: ) */ /** * Contains XMLHttpRequest.js * Copyright 2007 Sergey Ilinsky (http://www.ilinsky.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 */ /** * OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is * Copyright (c) 2006, Yahoo! Inc. * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or * without modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * * Neither the name of Yahoo! Inc. nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission of Yahoo! Inc. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* ====================================================================== OpenLayers/SingleFile.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ var OpenLayers = { /** * Constant: VERSION_NUMBER */ VERSION_NUMBER: "Release 2.13.1", /** * Constant: singleFile * TODO: remove this in 3.0 when we stop supporting build profiles that * include OpenLayers.js */ singleFile: true, /** * Method: _getScriptLocation * Return the path to this script. This is also implemented in * OpenLayers.js * * Returns: * {String} Path to this script */ _getScriptLocation: (function() { var r = new RegExp("(^|(.*?\\/))(OpenLayers[^\\/]*?\\.js)(\\?|$)"), s = document.getElementsByTagName('script'), src, m, l = ""; for(var i=0, len=s.length; i * * (end code) * * Please remember that when your OpenLayers script is not named * "OpenLayers.js" you will have to make sure that the default theme is * loaded into the page by including an appropriate -tag, * e.g.: * * (code) * * (end code) */ ImgPath : '' }; /* ====================================================================== OpenLayers/BaseTypes.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/SingleFile.js */ /** * Header: OpenLayers Base Types * OpenLayers custom string, number and function functions are described here. */ /** * Namespace: OpenLayers.String * Contains convenience functions for string manipulation. */ OpenLayers.String = { /** * APIFunction: startsWith * Test whether a string starts with another string. * * Parameters: * str - {String} The string to test. * sub - {String} The substring to look for. * * Returns: * {Boolean} The first string starts with the second. */ startsWith: function(str, sub) { return (str.indexOf(sub) == 0); }, /** * APIFunction: contains * Test whether a string contains another string. * * Parameters: * str - {String} The string to test. * sub - {String} The substring to look for. * * Returns: * {Boolean} The first string contains the second. */ contains: function(str, sub) { return (str.indexOf(sub) != -1); }, /** * APIFunction: trim * Removes leading and trailing whitespace characters from a string. * * Parameters: * str - {String} The (potentially) space padded string. This string is not * modified. * * Returns: * {String} A trimmed version of the string with all leading and * trailing spaces removed. */ trim: function(str) { return str.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); }, /** * APIFunction: camelize * Camel-case a hyphenated string. * Ex. "chicken-head" becomes "chickenHead", and * "-chicken-head" becomes "ChickenHead". * * Parameters: * str - {String} The string to be camelized. The original is not modified. * * Returns: * {String} The string, camelized */ camelize: function(str) { var oStringList = str.split('-'); var camelizedString = oStringList[0]; for (var i=1, len=oStringList.length; i replacement = context[a]; // 1 -> replacement = context[a][b]; // 2 -> replacement = context[a][b][c]; var subs = match.split(/\.+/); for (var i=0; i< subs.length; i++) { if (i == 0) { replacement = context; } if (replacement === undefined) { break; } replacement = replacement[subs[i]]; } if(typeof replacement == "function") { replacement = args ? replacement.apply(null, args) : replacement(); } // If replacement is undefined, return the string 'undefined'. // This is a workaround for a bugs in browsers not properly // dealing with non-participating groups in regular expressions: // http://blog.stevenlevithan.com/archives/npcg-javascript if (typeof replacement == 'undefined') { return 'undefined'; } else { return replacement; } }; return template.replace(OpenLayers.String.tokenRegEx, replacer); }, /** * Property: tokenRegEx * Used to find tokens in a string. * Examples: ${a}, ${a.b.c}, ${a-b}, ${5} */ tokenRegEx: /\$\{([\w.]+?)\}/g, /** * Property: numberRegEx * Used to test strings as numbers. */ numberRegEx: /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/, /** * APIFunction: isNumeric * Determine whether a string contains only a numeric value. * * Examples: * (code) * OpenLayers.String.isNumeric("6.02e23") // true * OpenLayers.String.isNumeric("12 dozen") // false * OpenLayers.String.isNumeric("4") // true * OpenLayers.String.isNumeric(" 4 ") // false * (end) * * Returns: * {Boolean} String contains only a number. */ isNumeric: function(value) { return OpenLayers.String.numberRegEx.test(value); }, /** * APIFunction: numericIf * Converts a string that appears to be a numeric value into a number. * * Parameters: * value - {String} * trimWhitespace - {Boolean} * * Returns: * {Number|String} a Number if the passed value is a number, a String * otherwise. */ numericIf: function(value, trimWhitespace) { var originalValue = value; if (trimWhitespace === true && value != null && value.replace) { value = value.replace(/^\s*|\s*$/g, ""); } return OpenLayers.String.isNumeric(value) ? parseFloat(value) : originalValue; } }; /** * Namespace: OpenLayers.Number * Contains convenience functions for manipulating numbers. */ OpenLayers.Number = { /** * Property: decimalSeparator * Decimal separator to use when formatting numbers. */ decimalSeparator: ".", /** * Property: thousandsSeparator * Thousands separator to use when formatting numbers. */ thousandsSeparator: ",", /** * APIFunction: limitSigDigs * Limit the number of significant digits on a float. * * Parameters: * num - {Float} * sig - {Integer} * * Returns: * {Float} The number, rounded to the specified number of significant * digits. */ limitSigDigs: function(num, sig) { var fig = 0; if (sig > 0) { fig = parseFloat(num.toPrecision(sig)); } return fig; }, /** * APIFunction: format * Formats a number for output. * * Parameters: * num - {Float} * dec - {Integer} Number of decimal places to round to. * Defaults to 0. Set to null to leave decimal places unchanged. * tsep - {String} Thousands separator. * Default is ",". * dsep - {String} Decimal separator. * Default is ".". * * Returns: * {String} A string representing the formatted number. */ format: function(num, dec, tsep, dsep) { dec = (typeof dec != "undefined") ? dec : 0; tsep = (typeof tsep != "undefined") ? tsep : OpenLayers.Number.thousandsSeparator; dsep = (typeof dsep != "undefined") ? dsep : OpenLayers.Number.decimalSeparator; if (dec != null) { num = parseFloat(num.toFixed(dec)); } var parts = num.toString().split("."); if (parts.length == 1 && dec == null) { // integer where we do not want to touch the decimals dec = 0; } var integer = parts[0]; if (tsep) { var thousands = /(-?[0-9]+)([0-9]{3})/; while(thousands.test(integer)) { integer = integer.replace(thousands, "$1" + tsep + "$2"); } } var str; if (dec == 0) { str = integer; } else { var rem = parts.length > 1 ? parts[1] : "0"; if (dec != null) { rem = rem + new Array(dec - rem.length + 1).join("0"); } str = integer + dsep + rem; } return str; }, /** * Method: zeroPad * Create a zero padded string optionally with a radix for casting numbers. * * Parameters: * num - {Number} The number to be zero padded. * len - {Number} The length of the string to be returned. * radix - {Number} An integer between 2 and 36 specifying the base to use * for representing numeric values. */ zeroPad: function(num, len, radix) { var str = num.toString(radix || 10); while (str.length < len) { str = "0" + str; } return str; } }; /** * Namespace: OpenLayers.Function * Contains convenience functions for function manipulation. */ OpenLayers.Function = { /** * APIFunction: bind * Bind a function to an object. Method to easily create closures with * 'this' altered. * * Parameters: * func - {Function} Input function. * object - {Object} The object to bind to the input function (as this). * * Returns: * {Function} A closure with 'this' set to the passed in object. */ bind: function(func, object) { // create a reference to all arguments past the second one var args = Array.prototype.slice.apply(arguments, [2]); return function() { // Push on any additional arguments from the actual function call. // These will come after those sent to the bind call. var newArgs = args.concat( Array.prototype.slice.apply(arguments, [0]) ); return func.apply(object, newArgs); }; }, /** * APIFunction: bindAsEventListener * Bind a function to an object, and configure it to receive the event * object as first parameter when called. * * Parameters: * func - {Function} Input function to serve as an event listener. * object - {Object} A reference to this. * * Returns: * {Function} */ bindAsEventListener: function(func, object) { return function(event) { return func.call(object, event || window.event); }; }, /** * APIFunction: False * A simple function to that just does "return false". We use this to * avoid attaching anonymous functions to DOM event handlers, which * causes "issues" on IE<8. * * Usage: * document.onclick = OpenLayers.Function.False; * * Returns: * {Boolean} */ False : function() { return false; }, /** * APIFunction: True * A simple function to that just does "return true". We use this to * avoid attaching anonymous functions to DOM event handlers, which * causes "issues" on IE<8. * * Usage: * document.onclick = OpenLayers.Function.True; * * Returns: * {Boolean} */ True : function() { return true; }, /** * APIFunction: Void * A reusable function that returns ``undefined``. * * Returns: * {undefined} */ Void: function() {} }; /** * Namespace: OpenLayers.Array * Contains convenience functions for array manipulation. */ OpenLayers.Array = { /** * APIMethod: filter * Filter an array. Provides the functionality of the * Array.prototype.filter extension to the ECMA-262 standard. Where * available, Array.prototype.filter will be used. * * Based on well known example from http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/filter * * Parameters: * array - {Array} The array to be filtered. This array is not mutated. * Elements added to this array by the callback will not be visited. * callback - {Function} A function that is called for each element in * the array. If this function returns true, the element will be * included in the return. The function will be called with three * arguments: the element in the array, the index of that element, and * the array itself. If the optional caller parameter is specified * the callback will be called with this set to caller. * caller - {Object} Optional object to be set as this when the callback * is called. * * Returns: * {Array} An array of elements from the passed in array for which the * callback returns true. */ filter: function(array, callback, caller) { var selected = []; if (Array.prototype.filter) { selected = array.filter(callback, caller); } else { var len = array.length; if (typeof callback != "function") { throw new TypeError(); } for(var i=0; i 1) { var newArgs = [C, P].concat( Array.prototype.slice.call(arguments).slice(1, len-1), F); OpenLayers.inherit.apply(null, newArgs); } else { C.prototype = F; } return C; }; /** * Function: OpenLayers.inherit * * Parameters: * C - {Object} the class that inherits * P - {Object} the superclass to inherit from * * In addition to the mandatory C and P parameters, an arbitrary number of * objects can be passed, which will extend C. */ OpenLayers.inherit = function(C, P) { var F = function() {}; F.prototype = P.prototype; C.prototype = new F; var i, l, o; for(i=2, l=arguments.length; i} A cached center location. This should not be * accessed directly. Use instead. */ centerLonLat: null, /** * Constructor: OpenLayers.Bounds * Construct a new bounds object. Coordinates can either be passed as four * arguments, or as a single argument. * * Parameters (four arguments): * left - {Number} The left bounds of the box. Note that for width * calculations, this is assumed to be less than the right value. * bottom - {Number} The bottom bounds of the box. Note that for height * calculations, this is assumed to be less than the top value. * right - {Number} The right bounds. * top - {Number} The top bounds. * * Parameters (single argument): * bounds - {Array(Number)} [left, bottom, right, top] */ initialize: function(left, bottom, right, top) { if (OpenLayers.Util.isArray(left)) { top = left[3]; right = left[2]; bottom = left[1]; left = left[0]; } if (left != null) { this.left = OpenLayers.Util.toFloat(left); } if (bottom != null) { this.bottom = OpenLayers.Util.toFloat(bottom); } if (right != null) { this.right = OpenLayers.Util.toFloat(right); } if (top != null) { this.top = OpenLayers.Util.toFloat(top); } }, /** * Method: clone * Create a cloned instance of this bounds. * * Returns: * {} A fresh copy of the bounds */ clone:function() { return new OpenLayers.Bounds(this.left, this.bottom, this.right, this.top); }, /** * Method: equals * Test a two bounds for equivalence. * * Parameters: * bounds - {} * * Returns: * {Boolean} The passed-in bounds object has the same left, * right, top, bottom components as this. Note that if bounds * passed in is null, returns false. */ equals:function(bounds) { var equals = false; if (bounds != null) { equals = ((this.left == bounds.left) && (this.right == bounds.right) && (this.top == bounds.top) && (this.bottom == bounds.bottom)); } return equals; }, /** * APIMethod: toString * Returns a string representation of the bounds object. * * Returns: * {String} String representation of bounds object. */ toString:function() { return [this.left, this.bottom, this.right, this.top].join(","); }, /** * APIMethod: toArray * Returns an array representation of the bounds object. * * Returns an array of left, bottom, right, top properties, or -- when the * optional parameter is true -- an array of the bottom, left, top, * right properties. * * Parameters: * reverseAxisOrder - {Boolean} Should we reverse the axis order? * * Returns: * {Array} array of left, bottom, right, top */ toArray: function(reverseAxisOrder) { if (reverseAxisOrder === true) { return [this.bottom, this.left, this.top, this.right]; } else { return [this.left, this.bottom, this.right, this.top]; } }, /** * APIMethod: toBBOX * Returns a boundingbox-string representation of the bounds object. * * Parameters: * decimal - {Integer} How many significant digits in the bbox coords? * Default is 6 * reverseAxisOrder - {Boolean} Should we reverse the axis order? * * Returns: * {String} Simple String representation of bounds object. * (e.g. "5,42,10,45") */ toBBOX:function(decimal, reverseAxisOrder) { if (decimal== null) { decimal = 6; } var mult = Math.pow(10, decimal); var xmin = Math.round(this.left * mult) / mult; var ymin = Math.round(this.bottom * mult) / mult; var xmax = Math.round(this.right * mult) / mult; var ymax = Math.round(this.top * mult) / mult; if (reverseAxisOrder === true) { return ymin + "," + xmin + "," + ymax + "," + xmax; } else { return xmin + "," + ymin + "," + xmax + "," + ymax; } }, /** * APIMethod: toGeometry * Create a new polygon geometry based on this bounds. * * Returns: * {} A new polygon with the coordinates * of this bounds. */ toGeometry: function() { return new OpenLayers.Geometry.Polygon([ new OpenLayers.Geometry.LinearRing([ new OpenLayers.Geometry.Point(this.left, this.bottom), new OpenLayers.Geometry.Point(this.right, this.bottom), new OpenLayers.Geometry.Point(this.right, this.top), new OpenLayers.Geometry.Point(this.left, this.top) ]) ]); }, /** * APIMethod: getWidth * Returns the width of the bounds. * * Returns: * {Float} The width of the bounds (right minus left). */ getWidth:function() { return (this.right - this.left); }, /** * APIMethod: getHeight * Returns the height of the bounds. * * Returns: * {Float} The height of the bounds (top minus bottom). */ getHeight:function() { return (this.top - this.bottom); }, /** * APIMethod: getSize * Returns an object of the bounds. * * Returns: * {} The size of the bounds. */ getSize:function() { return new OpenLayers.Size(this.getWidth(), this.getHeight()); }, /** * APIMethod: getCenterPixel * Returns the object which represents the center of the * bounds. * * Returns: * {} The center of the bounds in pixel space. */ getCenterPixel:function() { return new OpenLayers.Pixel( (this.left + this.right) / 2, (this.bottom + this.top) / 2); }, /** * APIMethod: getCenterLonLat * Returns the object which represents the center of the * bounds. * * Returns: * {} The center of the bounds in map space. */ getCenterLonLat:function() { if(!this.centerLonLat) { this.centerLonLat = new OpenLayers.LonLat( (this.left + this.right) / 2, (this.bottom + this.top) / 2 ); } return this.centerLonLat; }, /** * APIMethod: scale * Scales the bounds around a pixel or lonlat. Note that the new * bounds may return non-integer properties, even if a pixel * is passed. * * Parameters: * ratio - {Float} * origin - { or } * Default is center. * * Returns: * {} A new bounds that is scaled by ratio * from origin. */ scale: function(ratio, origin){ if(origin == null){ origin = this.getCenterLonLat(); } var origx,origy; // get origin coordinates if(origin.CLASS_NAME == "OpenLayers.LonLat"){ origx = origin.lon; origy = origin.lat; } else { origx = origin.x; origy = origin.y; } var left = (this.left - origx) * ratio + origx; var bottom = (this.bottom - origy) * ratio + origy; var right = (this.right - origx) * ratio + origx; var top = (this.top - origy) * ratio + origy; return new OpenLayers.Bounds(left, bottom, right, top); }, /** * APIMethod: add * Shifts the coordinates of the bound by the given horizontal and vertical * deltas. * * (start code) * var bounds = new OpenLayers.Bounds(0, 0, 10, 10); * bounds.toString(); * // => "0,0,10,10" * * bounds.add(-1.5, 4).toString(); * // => "-1.5,4,8.5,14" * (end) * * This method will throw a TypeError if it is passed null as an argument. * * Parameters: * x - {Float} horizontal delta * y - {Float} vertical delta * * Returns: * {} A new bounds whose coordinates are the same as * this, but shifted by the passed-in x and y values. */ add:function(x, y) { if ( (x == null) || (y == null) ) { throw new TypeError('Bounds.add cannot receive null values'); } return new OpenLayers.Bounds(this.left + x, this.bottom + y, this.right + x, this.top + y); }, /** * APIMethod: extend * Extend the bounds to include the , * or specified. * * Please note that this function assumes that left < right and * bottom < top. * * Parameters: * object - {, or * } The object to be included in the new bounds * object. */ extend:function(object) { if (object) { switch(object.CLASS_NAME) { case "OpenLayers.LonLat": this.extendXY(object.lon, object.lat); break; case "OpenLayers.Geometry.Point": this.extendXY(object.x, object.y); break; case "OpenLayers.Bounds": // clear cached center location this.centerLonLat = null; if ( (this.left == null) || (object.left < this.left)) { this.left = object.left; } if ( (this.bottom == null) || (object.bottom < this.bottom) ) { this.bottom = object.bottom; } if ( (this.right == null) || (object.right > this.right) ) { this.right = object.right; } if ( (this.top == null) || (object.top > this.top) ) { this.top = object.top; } break; } } }, /** * APIMethod: extendXY * Extend the bounds to include the XY coordinate specified. * * Parameters: * x - {number} The X part of the the coordinate. * y - {number} The Y part of the the coordinate. */ extendXY:function(x, y) { // clear cached center location this.centerLonLat = null; if ((this.left == null) || (x < this.left)) { this.left = x; } if ((this.bottom == null) || (y < this.bottom)) { this.bottom = y; } if ((this.right == null) || (x > this.right)) { this.right = x; } if ((this.top == null) || (y > this.top)) { this.top = y; } }, /** * APIMethod: containsLonLat * Returns whether the bounds object contains the given . * * Parameters: * ll - {|Object} OpenLayers.LonLat or an * object with a 'lon' and 'lat' properties. * options - {Object} Optional parameters * * Acceptable options: * inclusive - {Boolean} Whether or not to include the border. * Default is true. * worldBounds - {} If a worldBounds is provided, the * ll will be considered as contained if it exceeds the world bounds, * but can be wrapped around the dateline so it is contained by this * bounds. * * Returns: * {Boolean} The passed-in lonlat is within this bounds. */ containsLonLat: function(ll, options) { if (typeof options === "boolean") { options = {inclusive: options}; } options = options || {}; var contains = this.contains(ll.lon, ll.lat, options.inclusive), worldBounds = options.worldBounds; if (worldBounds && !contains) { var worldWidth = worldBounds.getWidth(); var worldCenterX = (worldBounds.left + worldBounds.right) / 2; var worldsAway = Math.round((ll.lon - worldCenterX) / worldWidth); contains = this.containsLonLat({ lon: ll.lon - worldsAway * worldWidth, lat: ll.lat }, {inclusive: options.inclusive}); } return contains; }, /** * APIMethod: containsPixel * Returns whether the bounds object contains the given . * * Parameters: * px - {} * inclusive - {Boolean} Whether or not to include the border. Default is * true. * * Returns: * {Boolean} The passed-in pixel is within this bounds. */ containsPixel:function(px, inclusive) { return this.contains(px.x, px.y, inclusive); }, /** * APIMethod: contains * Returns whether the bounds object contains the given x and y. * * Parameters: * x - {Float} * y - {Float} * inclusive - {Boolean} Whether or not to include the border. Default is * true. * * Returns: * {Boolean} Whether or not the passed-in coordinates are within this * bounds. */ contains:function(x, y, inclusive) { //set default if (inclusive == null) { inclusive = true; } if (x == null || y == null) { return false; } x = OpenLayers.Util.toFloat(x); y = OpenLayers.Util.toFloat(y); var contains = false; if (inclusive) { contains = ((x >= this.left) && (x <= this.right) && (y >= this.bottom) && (y <= this.top)); } else { contains = ((x > this.left) && (x < this.right) && (y > this.bottom) && (y < this.top)); } return contains; }, /** * APIMethod: intersectsBounds * Determine whether the target bounds intersects this bounds. Bounds are * considered intersecting if any of their edges intersect or if one * bounds contains the other. * * Parameters: * bounds - {} The target bounds. * options - {Object} Optional parameters. * * Acceptable options: * inclusive - {Boolean} Treat coincident borders as intersecting. Default * is true. If false, bounds that do not overlap but only touch at the * border will not be considered as intersecting. * worldBounds - {} If a worldBounds is provided, two * bounds will be considered as intersecting if they intersect when * shifted to within the world bounds. This applies only to bounds that * cross or are completely outside the world bounds. * * Returns: * {Boolean} The passed-in bounds object intersects this bounds. */ intersectsBounds:function(bounds, options) { if (typeof options === "boolean") { options = {inclusive: options}; } options = options || {}; if (options.worldBounds) { var self = this.wrapDateLine(options.worldBounds); bounds = bounds.wrapDateLine(options.worldBounds); } else { self = this; } if (options.inclusive == null) { options.inclusive = true; } var intersects = false; var mightTouch = ( self.left == bounds.right || self.right == bounds.left || self.top == bounds.bottom || self.bottom == bounds.top ); // if the two bounds only touch at an edge, and inclusive is false, // then the bounds don't *really* intersect. if (options.inclusive || !mightTouch) { // otherwise, if one of the boundaries even partially contains another, // inclusive of the edges, then they do intersect. var inBottom = ( ((bounds.bottom >= self.bottom) && (bounds.bottom <= self.top)) || ((self.bottom >= bounds.bottom) && (self.bottom <= bounds.top)) ); var inTop = ( ((bounds.top >= self.bottom) && (bounds.top <= self.top)) || ((self.top > bounds.bottom) && (self.top < bounds.top)) ); var inLeft = ( ((bounds.left >= self.left) && (bounds.left <= self.right)) || ((self.left >= bounds.left) && (self.left <= bounds.right)) ); var inRight = ( ((bounds.right >= self.left) && (bounds.right <= self.right)) || ((self.right >= bounds.left) && (self.right <= bounds.right)) ); intersects = ((inBottom || inTop) && (inLeft || inRight)); } // document me if (options.worldBounds && !intersects) { var world = options.worldBounds; var width = world.getWidth(); var selfCrosses = !world.containsBounds(self); var boundsCrosses = !world.containsBounds(bounds); if (selfCrosses && !boundsCrosses) { bounds = bounds.add(-width, 0); intersects = self.intersectsBounds(bounds, {inclusive: options.inclusive}); } else if (boundsCrosses && !selfCrosses) { self = self.add(-width, 0); intersects = bounds.intersectsBounds(self, {inclusive: options.inclusive}); } } return intersects; }, /** * APIMethod: containsBounds * Returns whether the bounds object contains the given . * * bounds - {} The target bounds. * partial - {Boolean} If any of the target corners is within this bounds * consider the bounds contained. Default is false. If false, the * entire target bounds must be contained within this bounds. * inclusive - {Boolean} Treat shared edges as contained. Default is * true. * * Returns: * {Boolean} The passed-in bounds object is contained within this bounds. */ containsBounds:function(bounds, partial, inclusive) { if (partial == null) { partial = false; } if (inclusive == null) { inclusive = true; } var bottomLeft = this.contains(bounds.left, bounds.bottom, inclusive); var bottomRight = this.contains(bounds.right, bounds.bottom, inclusive); var topLeft = this.contains(bounds.left, bounds.top, inclusive); var topRight = this.contains(bounds.right, bounds.top, inclusive); return (partial) ? (bottomLeft || bottomRight || topLeft || topRight) : (bottomLeft && bottomRight && topLeft && topRight); }, /** * APIMethod: determineQuadrant * Returns the the quadrant ("br", "tr", "tl", "bl") in which the given * lies. * * Parameters: * lonlat - {} * * Returns: * {String} The quadrant ("br" "tr" "tl" "bl") of the bounds in which the * coordinate lies. */ determineQuadrant: function(lonlat) { var quadrant = ""; var center = this.getCenterLonLat(); quadrant += (lonlat.lat < center.lat) ? "b" : "t"; quadrant += (lonlat.lon < center.lon) ? "l" : "r"; return quadrant; }, /** * APIMethod: transform * Transform the Bounds object from source to dest. * * Parameters: * source - {} Source projection. * dest - {} Destination projection. * * Returns: * {} Itself, for use in chaining operations. */ transform: function(source, dest) { // clear cached center location this.centerLonLat = null; var ll = OpenLayers.Projection.transform( {'x': this.left, 'y': this.bottom}, source, dest); var lr = OpenLayers.Projection.transform( {'x': this.right, 'y': this.bottom}, source, dest); var ul = OpenLayers.Projection.transform( {'x': this.left, 'y': this.top}, source, dest); var ur = OpenLayers.Projection.transform( {'x': this.right, 'y': this.top}, source, dest); this.left = Math.min(ll.x, ul.x); this.bottom = Math.min(ll.y, lr.y); this.right = Math.max(lr.x, ur.x); this.top = Math.max(ul.y, ur.y); return this; }, /** * APIMethod: wrapDateLine * Wraps the bounds object around the dateline. * * Parameters: * maxExtent - {} * options - {Object} Some possible options are: * * Allowed Options: * leftTolerance - {float} Allow for a margin of error * with the 'left' value of this * bound. * Default is 0. * rightTolerance - {float} Allow for a margin of error * with the 'right' value of * this bound. * Default is 0. * * Returns: * {} A copy of this bounds, but wrapped around the * "dateline" (as specified by the borders of * maxExtent). Note that this function only returns * a different bounds value if this bounds is * *entirely* outside of the maxExtent. If this * bounds straddles the dateline (is part in/part * out of maxExtent), the returned bounds will always * cross the left edge of the given maxExtent. *. */ wrapDateLine: function(maxExtent, options) { options = options || {}; var leftTolerance = options.leftTolerance || 0; var rightTolerance = options.rightTolerance || 0; var newBounds = this.clone(); if (maxExtent) { var width = maxExtent.getWidth(); //shift right? while (newBounds.left < maxExtent.left && newBounds.right - rightTolerance <= maxExtent.left ) { newBounds = newBounds.add(width, 0); } //shift left? while (newBounds.left + leftTolerance >= maxExtent.right && newBounds.right > maxExtent.right ) { newBounds = newBounds.add(-width, 0); } // crosses right only? force left var newLeft = newBounds.left + leftTolerance; if (newLeft < maxExtent.right && newLeft > maxExtent.left && newBounds.right - rightTolerance > maxExtent.right) { newBounds = newBounds.add(-width, 0); } } return newBounds; }, CLASS_NAME: "OpenLayers.Bounds" }); /** * APIFunction: fromString * Alternative constructor that builds a new OpenLayers.Bounds from a * parameter string. * * (begin code) * OpenLayers.Bounds.fromString("5,42,10,45"); * // => equivalent to ... * new OpenLayers.Bounds(5, 42, 10, 45); * (end) * * Parameters: * str - {String} Comma-separated bounds string. (e.g. "5,42,10,45") * reverseAxisOrder - {Boolean} Does the string use reverse axis order? * * Returns: * {} New bounds object built from the * passed-in String. */ OpenLayers.Bounds.fromString = function(str, reverseAxisOrder) { var bounds = str.split(","); return OpenLayers.Bounds.fromArray(bounds, reverseAxisOrder); }; /** * APIFunction: fromArray * Alternative constructor that builds a new OpenLayers.Bounds from an array. * * (begin code) * OpenLayers.Bounds.fromArray( [5, 42, 10, 45] ); * // => equivalent to ... * new OpenLayers.Bounds(5, 42, 10, 45); * (end) * * Parameters: * bbox - {Array(Float)} Array of bounds values (e.g. [5,42,10,45]) * reverseAxisOrder - {Boolean} Does the array use reverse axis order? * * Returns: * {} New bounds object built from the passed-in Array. */ OpenLayers.Bounds.fromArray = function(bbox, reverseAxisOrder) { return reverseAxisOrder === true ? new OpenLayers.Bounds(bbox[1], bbox[0], bbox[3], bbox[2]) : new OpenLayers.Bounds(bbox[0], bbox[1], bbox[2], bbox[3]); }; /** * APIFunction: fromSize * Alternative constructor that builds a new OpenLayers.Bounds from a size. * * (begin code) * OpenLayers.Bounds.fromSize( new OpenLayers.Size(10, 20) ); * // => equivalent to ... * new OpenLayers.Bounds(0, 20, 10, 0); * (end) * * Parameters: * size - { or Object} or an object with * both 'w' and 'h' properties. * * Returns: * {} New bounds object built from the passed-in size. */ OpenLayers.Bounds.fromSize = function(size) { return new OpenLayers.Bounds(0, size.h, size.w, 0); }; /** * Function: oppositeQuadrant * Get the opposite quadrant for a given quadrant string. * * (begin code) * OpenLayers.Bounds.oppositeQuadrant( "tl" ); * // => "br" * * OpenLayers.Bounds.oppositeQuadrant( "tr" ); * // => "bl" * (end) * * Parameters: * quadrant - {String} two character quadrant shortstring * * Returns: * {String} The opposing quadrant ("br" "tr" "tl" "bl"). For Example, if * you pass in "bl" it returns "tr", if you pass in "br" it * returns "tl", etc. */ OpenLayers.Bounds.oppositeQuadrant = function(quadrant) { var opp = ""; opp += (quadrant.charAt(0) == 't') ? 'b' : 't'; opp += (quadrant.charAt(1) == 'l') ? 'r' : 'l'; return opp; }; /* ====================================================================== OpenLayers/BaseTypes/Element.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Util.js * @requires OpenLayers/BaseTypes.js */ /** * Namespace: OpenLayers.Element */ OpenLayers.Element = { /** * APIFunction: visible * * Parameters: * element - {DOMElement} * * Returns: * {Boolean} Is the element visible? */ visible: function(element) { return OpenLayers.Util.getElement(element).style.display != 'none'; }, /** * APIFunction: toggle * Toggle the visibility of element(s) passed in * * Parameters: * element - {DOMElement} Actually user can pass any number of elements */ toggle: function() { for (var i=0, len=arguments.length; i"lon=5,lat=42") */ toString:function() { return ("lon=" + this.lon + ",lat=" + this.lat); }, /** * APIMethod: toShortString * * Returns: * {String} Shortened String representation of OpenLayers.LonLat object. * (e.g. "5, 42") */ toShortString:function() { return (this.lon + ", " + this.lat); }, /** * APIMethod: clone * * Returns: * {} New OpenLayers.LonLat object with the same lon * and lat values */ clone:function() { return new OpenLayers.LonLat(this.lon, this.lat); }, /** * APIMethod: add * * Parameters: * lon - {Float} * lat - {Float} * * Returns: * {} A new OpenLayers.LonLat object with the lon and * lat passed-in added to this's. */ add:function(lon, lat) { if ( (lon == null) || (lat == null) ) { throw new TypeError('LonLat.add cannot receive null values'); } return new OpenLayers.LonLat(this.lon + OpenLayers.Util.toFloat(lon), this.lat + OpenLayers.Util.toFloat(lat)); }, /** * APIMethod: equals * * Parameters: * ll - {} * * Returns: * {Boolean} Boolean value indicating whether the passed-in * object has the same lon and lat * components as this. * Note: if ll passed in is null, returns false */ equals:function(ll) { var equals = false; if (ll != null) { equals = ((this.lon == ll.lon && this.lat == ll.lat) || (isNaN(this.lon) && isNaN(this.lat) && isNaN(ll.lon) && isNaN(ll.lat))); } return equals; }, /** * APIMethod: transform * Transform the LonLat object from source to dest. This transformation is * *in place*: if you want a *new* lonlat, use .clone() first. * * Parameters: * source - {} Source projection. * dest - {} Destination projection. * * Returns: * {} Itself, for use in chaining operations. */ transform: function(source, dest) { var point = OpenLayers.Projection.transform( {'x': this.lon, 'y': this.lat}, source, dest); this.lon = point.x; this.lat = point.y; return this; }, /** * APIMethod: wrapDateLine * * Parameters: * maxExtent - {} * * Returns: * {} A copy of this lonlat, but wrapped around the * "dateline" (as specified by the borders of * maxExtent) */ wrapDateLine: function(maxExtent) { var newLonLat = this.clone(); if (maxExtent) { //shift right? while (newLonLat.lon < maxExtent.left) { newLonLat.lon += maxExtent.getWidth(); } //shift left? while (newLonLat.lon > maxExtent.right) { newLonLat.lon -= maxExtent.getWidth(); } } return newLonLat; }, CLASS_NAME: "OpenLayers.LonLat" }); /** * Function: fromString * Alternative constructor that builds a new from a * parameter string * * Parameters: * str - {String} Comma-separated Lon,Lat coordinate string. * (e.g. "5,40") * * Returns: * {} New object built from the * passed-in String. */ OpenLayers.LonLat.fromString = function(str) { var pair = str.split(","); return new OpenLayers.LonLat(pair[0], pair[1]); }; /** * Function: fromArray * Alternative constructor that builds a new from an * array of two numbers that represent lon- and lat-values. * * Parameters: * arr - {Array(Float)} Array of lon/lat values (e.g. [5,-42]) * * Returns: * {} New object built from the * passed-in array. */ OpenLayers.LonLat.fromArray = function(arr) { var gotArr = OpenLayers.Util.isArray(arr), lon = gotArr && arr[0], lat = gotArr && arr[1]; return new OpenLayers.LonLat(lon, lat); }; /* ====================================================================== OpenLayers/BaseTypes/Pixel.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/BaseTypes/Class.js */ /** * Class: OpenLayers.Pixel * This class represents a screen coordinate, in x and y coordinates */ OpenLayers.Pixel = OpenLayers.Class({ /** * APIProperty: x * {Number} The x coordinate */ x: 0.0, /** * APIProperty: y * {Number} The y coordinate */ y: 0.0, /** * Constructor: OpenLayers.Pixel * Create a new OpenLayers.Pixel instance * * Parameters: * x - {Number} The x coordinate * y - {Number} The y coordinate * * Returns: * An instance of OpenLayers.Pixel */ initialize: function(x, y) { this.x = parseFloat(x); this.y = parseFloat(y); }, /** * Method: toString * Cast this object into a string * * Returns: * {String} The string representation of Pixel. ex: "x=200.4,y=242.2" */ toString:function() { return ("x=" + this.x + ",y=" + this.y); }, /** * APIMethod: clone * Return a clone of this pixel object * * Returns: * {} A clone pixel */ clone:function() { return new OpenLayers.Pixel(this.x, this.y); }, /** * APIMethod: equals * Determine whether one pixel is equivalent to another * * Parameters: * px - {|Object} An OpenLayers.Pixel or an object with * a 'x' and 'y' properties. * * Returns: * {Boolean} The point passed in as parameter is equal to this. Note that * if px passed in is null, returns false. */ equals:function(px) { var equals = false; if (px != null) { equals = ((this.x == px.x && this.y == px.y) || (isNaN(this.x) && isNaN(this.y) && isNaN(px.x) && isNaN(px.y))); } return equals; }, /** * APIMethod: distanceTo * Returns the distance to the pixel point passed in as a parameter. * * Parameters: * px - {} * * Returns: * {Float} The pixel point passed in as parameter to calculate the * distance to. */ distanceTo:function(px) { return Math.sqrt( Math.pow(this.x - px.x, 2) + Math.pow(this.y - px.y, 2) ); }, /** * APIMethod: add * * Parameters: * x - {Integer} * y - {Integer} * * Returns: * {} A new Pixel with this pixel's x&y augmented by the * values passed in. */ add:function(x, y) { if ( (x == null) || (y == null) ) { throw new TypeError('Pixel.add cannot receive null values'); } return new OpenLayers.Pixel(this.x + x, this.y + y); }, /** * APIMethod: offset * * Parameters * px - {|Object} An OpenLayers.Pixel or an object with * a 'x' and 'y' properties. * * Returns: * {} A new Pixel with this pixel's x&y augmented by the * x&y values of the pixel passed in. */ offset:function(px) { var newPx = this.clone(); if (px) { newPx = this.add(px.x, px.y); } return newPx; }, CLASS_NAME: "OpenLayers.Pixel" }); /* ====================================================================== OpenLayers/BaseTypes/Size.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/BaseTypes/Class.js */ /** * Class: OpenLayers.Size * Instances of this class represent a width/height pair */ OpenLayers.Size = OpenLayers.Class({ /** * APIProperty: w * {Number} width */ w: 0.0, /** * APIProperty: h * {Number} height */ h: 0.0, /** * Constructor: OpenLayers.Size * Create an instance of OpenLayers.Size * * Parameters: * w - {Number} width * h - {Number} height */ initialize: function(w, h) { this.w = parseFloat(w); this.h = parseFloat(h); }, /** * Method: toString * Return the string representation of a size object * * Returns: * {String} The string representation of OpenLayers.Size object. * (e.g. "w=55,h=66") */ toString:function() { return ("w=" + this.w + ",h=" + this.h); }, /** * APIMethod: clone * Create a clone of this size object * * Returns: * {} A new OpenLayers.Size object with the same w and h * values */ clone:function() { return new OpenLayers.Size(this.w, this.h); }, /** * * APIMethod: equals * Determine where this size is equal to another * * Parameters: * sz - {|Object} An OpenLayers.Size or an object with * a 'w' and 'h' properties. * * Returns: * {Boolean} The passed in size has the same h and w properties as this one. * Note that if sz passed in is null, returns false. */ equals:function(sz) { var equals = false; if (sz != null) { equals = ((this.w == sz.w && this.h == sz.h) || (isNaN(this.w) && isNaN(this.h) && isNaN(sz.w) && isNaN(sz.h))); } return equals; }, CLASS_NAME: "OpenLayers.Size" }); /* ====================================================================== OpenLayers/Console.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/BaseTypes/Class.js */ /** * Namespace: OpenLayers.Console * The OpenLayers.Console namespace is used for debugging and error logging. * If the Firebug Lite (../Firebug/firebug.js) is included before this script, * calls to OpenLayers.Console methods will get redirected to window.console. * This makes use of the Firebug extension where available and allows for * cross-browser debugging Firebug style. * * Note: * Note that behavior will differ with the Firebug extention and Firebug Lite. * Most notably, the Firebug Lite console does not currently allow for * hyperlinks to code or for clicking on object to explore their properties. * */ OpenLayers.Console = { /** * Create empty functions for all console methods. The real value of these * properties will be set if Firebug Lite (../Firebug/firebug.js script) is * included. We explicitly require the Firebug Lite script to trigger * functionality of the OpenLayers.Console methods. */ /** * APIFunction: log * Log an object in the console. The Firebug Lite console logs string * representation of objects. Given multiple arguments, they will * be cast to strings and logged with a space delimiter. If the first * argument is a string with printf-like formatting, subsequent arguments * will be used in string substitution. Any additional arguments (beyond * the number substituted in a format string) will be appended in a space- * delimited line. * * Parameters: * object - {Object} */ log: function() {}, /** * APIFunction: debug * Writes a message to the console, including a hyperlink to the line * where it was called. * * May be called with multiple arguments as with OpenLayers.Console.log(). * * Parameters: * object - {Object} */ debug: function() {}, /** * APIFunction: info * Writes a message to the console with the visual "info" icon and color * coding and a hyperlink to the line where it was called. * * May be called with multiple arguments as with OpenLayers.Console.log(). * * Parameters: * object - {Object} */ info: function() {}, /** * APIFunction: warn * Writes a message to the console with the visual "warning" icon and * color coding and a hyperlink to the line where it was called. * * May be called with multiple arguments as with OpenLayers.Console.log(). * * Parameters: * object - {Object} */ warn: function() {}, /** * APIFunction: error * Writes a message to the console with the visual "error" icon and color * coding and a hyperlink to the line where it was called. * * May be called with multiple arguments as with OpenLayers.Console.log(). * * Parameters: * object - {Object} */ error: function() {}, /** * APIFunction: userError * A single interface for showing error messages to the user. The default * behavior is a Javascript alert, though this can be overridden by * reassigning OpenLayers.Console.userError to a different function. * * Expects a single error message * * Parameters: * error - {Object} */ userError: function(error) { alert(error); }, /** * APIFunction: assert * Tests that an expression is true. If not, it will write a message to * the console and throw an exception. * * May be called with multiple arguments as with OpenLayers.Console.log(). * * Parameters: * object - {Object} */ assert: function() {}, /** * APIFunction: dir * Prints an interactive listing of all properties of the object. This * looks identical to the view that you would see in the DOM tab. * * Parameters: * object - {Object} */ dir: function() {}, /** * APIFunction: dirxml * Prints the XML source tree of an HTML or XML element. This looks * identical to the view that you would see in the HTML tab. You can click * on any node to inspect it in the HTML tab. * * Parameters: * object - {Object} */ dirxml: function() {}, /** * APIFunction: trace * Prints an interactive stack trace of JavaScript execution at the point * where it is called. The stack trace details the functions on the stack, * as well as the values that were passed as arguments to each function. * You can click each function to take you to its source in the Script tab, * and click each argument value to inspect it in the DOM or HTML tabs. * */ trace: function() {}, /** * APIFunction: group * Writes a message to the console and opens a nested block to indent all * future messages sent to the console. Call OpenLayers.Console.groupEnd() * to close the block. * * May be called with multiple arguments as with OpenLayers.Console.log(). * * Parameters: * object - {Object} */ group: function() {}, /** * APIFunction: groupEnd * Closes the most recently opened block created by a call to * OpenLayers.Console.group */ groupEnd: function() {}, /** * APIFunction: time * Creates a new timer under the given name. Call * OpenLayers.Console.timeEnd(name) * with the same name to stop the timer and print the time elapsed. * * Parameters: * name - {String} */ time: function() {}, /** * APIFunction: timeEnd * Stops a timer created by a call to OpenLayers.Console.time(name) and * writes the time elapsed. * * Parameters: * name - {String} */ timeEnd: function() {}, /** * APIFunction: profile * Turns on the JavaScript profiler. The optional argument title would * contain the text to be printed in the header of the profile report. * * This function is not currently implemented in Firebug Lite. * * Parameters: * title - {String} Optional title for the profiler */ profile: function() {}, /** * APIFunction: profileEnd * Turns off the JavaScript profiler and prints its report. * * This function is not currently implemented in Firebug Lite. */ profileEnd: function() {}, /** * APIFunction: count * Writes the number of times that the line of code where count was called * was executed. The optional argument title will print a message in * addition to the number of the count. * * This function is not currently implemented in Firebug Lite. * * Parameters: * title - {String} Optional title to be printed with count */ count: function() {}, CLASS_NAME: "OpenLayers.Console" }; /** * Execute an anonymous function to extend the OpenLayers.Console namespace * if the firebug.js script is included. This closure is used so that the * "scripts" and "i" variables don't pollute the global namespace. */ (function() { /** * If Firebug Lite is included (before this script), re-route all * OpenLayers.Console calls to the console object. */ var scripts = document.getElementsByTagName("script"); for(var i=0, len=scripts.length; i method to set this value and the method to * retrieve it. */ code: null, /** * APIProperty: defaultCode * {String} Default language to use when a specific language can't be * found. Default is "en". */ defaultCode: "en", /** * APIFunction: getCode * Get the current language code. * * Returns: * {String} The current language code. */ getCode: function() { if(!OpenLayers.Lang.code) { OpenLayers.Lang.setCode(); } return OpenLayers.Lang.code; }, /** * APIFunction: setCode * Set the language code for string translation. This code is used by * the method. * * Parameters: * code - {String} These codes follow the IETF recommendations at * http://www.ietf.org/rfc/rfc3066.txt. If no value is set, the * browser's language setting will be tested. If no * dictionary exists for the code, the * will be used. */ setCode: function(code) { var lang; if(!code) { code = (OpenLayers.BROWSER_NAME == "msie") ? navigator.userLanguage : navigator.language; } var parts = code.split('-'); parts[0] = parts[0].toLowerCase(); if(typeof OpenLayers.Lang[parts[0]] == "object") { lang = parts[0]; } // check for regional extensions if(parts[1]) { var testLang = parts[0] + '-' + parts[1].toUpperCase(); if(typeof OpenLayers.Lang[testLang] == "object") { lang = testLang; } } if(!lang) { OpenLayers.Console.warn( 'Failed to find OpenLayers.Lang.' + parts.join("-") + ' dictionary, falling back to default language' ); lang = OpenLayers.Lang.defaultCode; } OpenLayers.Lang.code = lang; }, /** * APIMethod: translate * Looks up a key from a dictionary based on the current language string. * The value of will be used to determine the appropriate * dictionary. Dictionaries are stored in . * * Parameters: * key - {String} The key for an i18n string value in the dictionary. * context - {Object} Optional context to be used with * . * * Returns: * {String} A internationalized string. */ translate: function(key, context) { var dictionary = OpenLayers.Lang[OpenLayers.Lang.getCode()]; var message = dictionary && dictionary[key]; if(!message) { // Message not found, fall back to message key message = key; } if(context) { message = OpenLayers.String.format(message, context); } return message; } }; /** * APIMethod: OpenLayers.i18n * Alias for . Looks up a key from a dictionary * based on the current language string. The value of * will be used to determine the appropriate * dictionary. Dictionaries are stored in . * * Parameters: * key - {String} The key for an i18n string value in the dictionary. * context - {Object} Optional context to be used with * . * * Returns: * {String} A internationalized string. */ OpenLayers.i18n = OpenLayers.Lang.translate; /* ====================================================================== OpenLayers/Util.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/BaseTypes.js * @requires OpenLayers/BaseTypes/Bounds.js * @requires OpenLayers/BaseTypes/Element.js * @requires OpenLayers/BaseTypes/LonLat.js * @requires OpenLayers/BaseTypes/Pixel.js * @requires OpenLayers/BaseTypes/Size.js * @requires OpenLayers/Lang.js */ /** * Namespace: Util */ OpenLayers.Util = OpenLayers.Util || {}; /** * Function: getElement * This is the old $() from prototype * * Parameters: * e - {String or DOMElement or Window} * * Returns: * {Array(DOMElement) or DOMElement} */ OpenLayers.Util.getElement = function() { var elements = []; for (var i=0, len=arguments.length; i= 0; i--) { if(array[i] == item) { array.splice(i,1); //break;more than once?? } } return array; }; /** * Function: indexOf * Seems to exist already in FF, but not in MOZ. * * Parameters: * array - {Array} * obj - {*} * * Returns: * {Integer} The index at which the first object was found in the array. * If not found, returns -1. */ OpenLayers.Util.indexOf = function(array, obj) { // use the build-in function if available. if (typeof array.indexOf == "function") { return array.indexOf(obj); } else { for (var i = 0, len = array.length; i < len; i++) { if (array[i] == obj) { return i; } } return -1; } }; /** * Property: dotless * {RegExp} * Compiled regular expression to match dots ("."). This is used for replacing * dots in identifiers. Because object identifiers are frequently used for * DOM element identifiers by the library, we avoid using dots to make for * more sensible CSS selectors. * * TODO: Use a module pattern to avoid bloating the API with stuff like this. */ OpenLayers.Util.dotless = /\./g; /** * Function: modifyDOMElement * * Modifies many properties of a DOM element all at once. Passing in * null to an individual parameter will avoid setting the attribute. * * Parameters: * element - {DOMElement} DOM element to modify. * id - {String} The element id attribute to set. Note that dots (".") will be * replaced with underscore ("_") in setting the element id. * px - {|Object} The element left and top position, * OpenLayers.Pixel or an object with * a 'x' and 'y' properties. * sz - {|Object} The element width and height, * OpenLayers.Size or an object with a * 'w' and 'h' properties. * position - {String} The position attribute. eg: absolute, * relative, etc. * border - {String} The style.border attribute. eg: * solid black 2px * overflow - {String} The style.overview attribute. * opacity - {Float} Fractional value (0.0 - 1.0) */ OpenLayers.Util.modifyDOMElement = function(element, id, px, sz, position, border, overflow, opacity) { if (id) { element.id = id.replace(OpenLayers.Util.dotless, "_"); } if (px) { element.style.left = px.x + "px"; element.style.top = px.y + "px"; } if (sz) { element.style.width = sz.w + "px"; element.style.height = sz.h + "px"; } if (position) { element.style.position = position; } if (border) { element.style.border = border; } if (overflow) { element.style.overflow = overflow; } if (parseFloat(opacity) >= 0.0 && parseFloat(opacity) < 1.0) { element.style.filter = 'alpha(opacity=' + (opacity * 100) + ')'; element.style.opacity = opacity; } else if (parseFloat(opacity) == 1.0) { element.style.filter = ''; element.style.opacity = ''; } }; /** * Function: createDiv * Creates a new div and optionally set some standard attributes. * Null may be passed to each parameter if you do not wish to * set a particular attribute. * Note - zIndex is NOT set on the resulting div. * * Parameters: * id - {String} An identifier for this element. If no id is * passed an identifier will be created * automatically. Note that dots (".") will be replaced with * underscore ("_") when generating ids. * px - {|Object} The element left and top position, * OpenLayers.Pixel or an object with * a 'x' and 'y' properties. * sz - {|Object} The element width and height, * OpenLayers.Size or an object with a * 'w' and 'h' properties. * imgURL - {String} A url pointing to an image to use as a * background image. * position - {String} The style.position value. eg: absolute, * relative etc. * border - {String} The the style.border value. * eg: 2px solid black * overflow - {String} The style.overflow value. Eg. hidden * opacity - {Float} Fractional value (0.0 - 1.0) * * Returns: * {DOMElement} A DOM Div created with the specified attributes. */ OpenLayers.Util.createDiv = function(id, px, sz, imgURL, position, border, overflow, opacity) { var dom = document.createElement('div'); if (imgURL) { dom.style.backgroundImage = 'url(' + imgURL + ')'; } //set generic properties if (!id) { id = OpenLayers.Util.createUniqueID("OpenLayersDiv"); } if (!position) { position = "absolute"; } OpenLayers.Util.modifyDOMElement(dom, id, px, sz, position, border, overflow, opacity); return dom; }; /** * Function: createImage * Creates an img element with specific attribute values. * * Parameters: * id - {String} The id field for the img. If none assigned one will be * automatically generated. * px - {|Object} The element left and top position, * OpenLayers.Pixel or an object with * a 'x' and 'y' properties. * sz - {|Object} The element width and height, * OpenLayers.Size or an object with a * 'w' and 'h' properties. * imgURL - {String} The url to use as the image source. * position - {String} The style.position value. * border - {String} The border to place around the image. * opacity - {Float} Fractional value (0.0 - 1.0) * delayDisplay - {Boolean} If true waits until the image has been * loaded. * * Returns: * {DOMElement} A DOM Image created with the specified attributes. */ OpenLayers.Util.createImage = function(id, px, sz, imgURL, position, border, opacity, delayDisplay) { var image = document.createElement("img"); //set generic properties if (!id) { id = OpenLayers.Util.createUniqueID("OpenLayersDiv"); } if (!position) { position = "relative"; } OpenLayers.Util.modifyDOMElement(image, id, px, sz, position, border, null, opacity); if (delayDisplay) { image.style.display = "none"; function display() { image.style.display = ""; OpenLayers.Event.stopObservingElement(image); } OpenLayers.Event.observe(image, "load", display); OpenLayers.Event.observe(image, "error", display); } //set special properties image.style.alt = id; image.galleryImg = "no"; if (imgURL) { image.src = imgURL; } return image; }; /** * Property: IMAGE_RELOAD_ATTEMPTS * {Integer} How many times should we try to reload an image before giving up? * Default is 0 */ OpenLayers.IMAGE_RELOAD_ATTEMPTS = 0; /** * Property: alphaHackNeeded * {Boolean} true if the png alpha hack is necessary and possible, false otherwise. */ OpenLayers.Util.alphaHackNeeded = null; /** * Function: alphaHack * Checks whether it's necessary (and possible) to use the png alpha * hack which allows alpha transparency for png images under Internet * Explorer. * * Returns: * {Boolean} true if the png alpha hack is necessary and possible, false otherwise. */ OpenLayers.Util.alphaHack = function() { if (OpenLayers.Util.alphaHackNeeded == null) { var arVersion = navigator.appVersion.split("MSIE"); var version = parseFloat(arVersion[1]); var filter = false; // IEs4Lin dies when trying to access document.body.filters, because // the property is there, but requires a DLL that can't be provided. This // means that we need to wrap this in a try/catch so that this can // continue. try { filter = !!(document.body.filters); } catch (e) {} OpenLayers.Util.alphaHackNeeded = (filter && (version >= 5.5) && (version < 7)); } return OpenLayers.Util.alphaHackNeeded; }; /** * Function: modifyAlphaImageDiv * * Parameters: * div - {DOMElement} Div containing Alpha-adjusted Image * id - {String} * px - {|Object} OpenLayers.Pixel or an object with * a 'x' and 'y' properties. * sz - {|Object} OpenLayers.Size or an object with * a 'w' and 'h' properties. * imgURL - {String} * position - {String} * border - {String} * sizing - {String} 'crop', 'scale', or 'image'. Default is "scale" * opacity - {Float} Fractional value (0.0 - 1.0) */ OpenLayers.Util.modifyAlphaImageDiv = function(div, id, px, sz, imgURL, position, border, sizing, opacity) { OpenLayers.Util.modifyDOMElement(div, id, px, sz, position, null, null, opacity); var img = div.childNodes[0]; if (imgURL) { img.src = imgURL; } OpenLayers.Util.modifyDOMElement(img, div.id + "_innerImage", null, sz, "relative", border); if (OpenLayers.Util.alphaHack()) { if(div.style.display != "none") { div.style.display = "inline-block"; } if (sizing == null) { sizing = "scale"; } div.style.filter = "progid:DXImageTransform.Microsoft" + ".AlphaImageLoader(src='" + img.src + "', " + "sizingMethod='" + sizing + "')"; if (parseFloat(div.style.opacity) >= 0.0 && parseFloat(div.style.opacity) < 1.0) { div.style.filter += " alpha(opacity=" + div.style.opacity * 100 + ")"; } img.style.filter = "alpha(opacity=0)"; } }; /** * Function: createAlphaImageDiv * * Parameters: * id - {String} * px - {|Object} OpenLayers.Pixel or an object with * a 'x' and 'y' properties. * sz - {|Object} OpenLayers.Size or an object with * a 'w' and 'h' properties. * imgURL - {String} * position - {String} * border - {String} * sizing - {String} 'crop', 'scale', or 'image'. Default is "scale" * opacity - {Float} Fractional value (0.0 - 1.0) * delayDisplay - {Boolean} If true waits until the image has been * loaded. * * Returns: * {DOMElement} A DOM Div created with a DOM Image inside it. If the hack is * needed for transparency in IE, it is added. */ OpenLayers.Util.createAlphaImageDiv = function(id, px, sz, imgURL, position, border, sizing, opacity, delayDisplay) { var div = OpenLayers.Util.createDiv(); var img = OpenLayers.Util.createImage(null, null, null, null, null, null, null, delayDisplay); img.className = "olAlphaImg"; div.appendChild(img); OpenLayers.Util.modifyAlphaImageDiv(div, id, px, sz, imgURL, position, border, sizing, opacity); return div; }; /** * Function: upperCaseObject * Creates a new hashtable and copies over all the keys from the * passed-in object, but storing them under an uppercased * version of the key at which they were stored. * * Parameters: * object - {Object} * * Returns: * {Object} A new Object with all the same keys but uppercased */ OpenLayers.Util.upperCaseObject = function (object) { var uObject = {}; for (var key in object) { uObject[key.toUpperCase()] = object[key]; } return uObject; }; /** * Function: applyDefaults * Takes an object and copies any properties that don't exist from * another properties, by analogy with OpenLayers.Util.extend() from * Prototype.js. * * Parameters: * to - {Object} The destination object. * from - {Object} The source object. Any properties of this object that * are undefined in the to object will be set on the to object. * * Returns: * {Object} A reference to the to object. Note that the to argument is modified * in place and returned by this function. */ OpenLayers.Util.applyDefaults = function (to, from) { to = to || {}; /* * FF/Windows < 2.0.0.13 reports "Illegal operation on WrappedNative * prototype object" when calling hawOwnProperty if the source object is an * instance of window.Event. */ var fromIsEvt = typeof window.Event == "function" && from instanceof window.Event; for (var key in from) { if (to[key] === undefined || (!fromIsEvt && from.hasOwnProperty && from.hasOwnProperty(key) && !to.hasOwnProperty(key))) { to[key] = from[key]; } } /** * IE doesn't include the toString property when iterating over an object's * properties with the for(property in object) syntax. Explicitly check if * the source has its own toString property. */ if(!fromIsEvt && from && from.hasOwnProperty && from.hasOwnProperty('toString') && !to.hasOwnProperty('toString')) { to.toString = from.toString; } return to; }; /** * Function: getParameterString * * Parameters: * params - {Object} * * Returns: * {String} A concatenation of the properties of an object in * http parameter notation. * (ex. "key1=value1&key2=value2&key3=value3") * If a parameter is actually a list, that parameter will then * be set to a comma-seperated list of values (foo,bar) instead * of being URL escaped (foo%3Abar). */ OpenLayers.Util.getParameterString = function(params) { var paramsArray = []; for (var key in params) { var value = params[key]; if ((value != null) && (typeof value != 'function')) { var encodedValue; if (typeof value == 'object' && value.constructor == Array) { /* value is an array; encode items and separate with "," */ var encodedItemArray = []; var item; for (var itemIndex=0, len=value.length; itemIndex} (or any object with both .lat, .lon properties) * p2 - {} (or any object with both .lat, .lon properties) * * Returns: * {Float} The distance (in km) between the two input points as measured on an * ellipsoid. Note that the input point objects must be in geographic * coordinates (decimal degrees) and the return distance is in kilometers. */ OpenLayers.Util.distVincenty = function(p1, p2) { var ct = OpenLayers.Util.VincentyConstants; var a = ct.a, b = ct.b, f = ct.f; var L = OpenLayers.Util.rad(p2.lon - p1.lon); var U1 = Math.atan((1-f) * Math.tan(OpenLayers.Util.rad(p1.lat))); var U2 = Math.atan((1-f) * Math.tan(OpenLayers.Util.rad(p2.lat))); var sinU1 = Math.sin(U1), cosU1 = Math.cos(U1); var sinU2 = Math.sin(U2), cosU2 = Math.cos(U2); var lambda = L, lambdaP = 2*Math.PI; var iterLimit = 20; while (Math.abs(lambda-lambdaP) > 1e-12 && --iterLimit>0) { var sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda); var sinSigma = Math.sqrt((cosU2*sinLambda) * (cosU2*sinLambda) + (cosU1*sinU2-sinU1*cosU2*cosLambda) * (cosU1*sinU2-sinU1*cosU2*cosLambda)); if (sinSigma==0) { return 0; // co-incident points } var cosSigma = sinU1*sinU2 + cosU1*cosU2*cosLambda; var sigma = Math.atan2(sinSigma, cosSigma); var alpha = Math.asin(cosU1 * cosU2 * sinLambda / sinSigma); var cosSqAlpha = Math.cos(alpha) * Math.cos(alpha); var cos2SigmaM = cosSigma - 2*sinU1*sinU2/cosSqAlpha; var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha)); lambdaP = lambda; lambda = L + (1-C) * f * Math.sin(alpha) * (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM))); } if (iterLimit==0) { return NaN; // formula failed to converge } var uSq = cosSqAlpha * (a*a - b*b) / (b*b); var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq))); var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq))); var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)- B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM))); var s = b*A*(sigma-deltaSigma); var d = s.toFixed(3)/1000; // round to 1mm precision return d; }; /** * APIFunction: destinationVincenty * Calculate destination point given start point lat/long (numeric degrees), * bearing (numeric degrees) & distance (in m). * Adapted from Chris Veness work, see * http://www.movable-type.co.uk/scripts/latlong-vincenty-direct.html * * Parameters: * lonlat - {} (or any object with both .lat, .lon * properties) The start point. * brng - {Float} The bearing (degrees). * dist - {Float} The ground distance (meters). * * Returns: * {} The destination point. */ OpenLayers.Util.destinationVincenty = function(lonlat, brng, dist) { var u = OpenLayers.Util; var ct = u.VincentyConstants; var a = ct.a, b = ct.b, f = ct.f; var lon1 = lonlat.lon; var lat1 = lonlat.lat; var s = dist; var alpha1 = u.rad(brng); var sinAlpha1 = Math.sin(alpha1); var cosAlpha1 = Math.cos(alpha1); var tanU1 = (1-f) * Math.tan(u.rad(lat1)); var cosU1 = 1 / Math.sqrt((1 + tanU1*tanU1)), sinU1 = tanU1*cosU1; var sigma1 = Math.atan2(tanU1, cosAlpha1); var sinAlpha = cosU1 * sinAlpha1; var cosSqAlpha = 1 - sinAlpha*sinAlpha; var uSq = cosSqAlpha * (a*a - b*b) / (b*b); var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq))); var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq))); var sigma = s / (b*A), sigmaP = 2*Math.PI; while (Math.abs(sigma-sigmaP) > 1e-12) { var cos2SigmaM = Math.cos(2*sigma1 + sigma); var sinSigma = Math.sin(sigma); var cosSigma = Math.cos(sigma); var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)- B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM))); sigmaP = sigma; sigma = s / (b*A) + deltaSigma; } var tmp = sinU1*sinSigma - cosU1*cosSigma*cosAlpha1; var lat2 = Math.atan2(sinU1*cosSigma + cosU1*sinSigma*cosAlpha1, (1-f)*Math.sqrt(sinAlpha*sinAlpha + tmp*tmp)); var lambda = Math.atan2(sinSigma*sinAlpha1, cosU1*cosSigma - sinU1*sinSigma*cosAlpha1); var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha)); var L = lambda - (1-C) * f * sinAlpha * (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM))); var revAz = Math.atan2(sinAlpha, -tmp); // final bearing return new OpenLayers.LonLat(lon1+u.deg(L), u.deg(lat2)); }; /** * Function: getParameters * Parse the parameters from a URL or from the current page itself into a * JavaScript Object. Note that parameter values with commas are separated * out into an Array. * * Parameters: * url - {String} Optional url used to extract the query string. * If url is null or is not supplied, query string is taken * from the page location. * options - {Object} Additional options. Optional. * * Valid options: * splitArgs - {Boolean} Split comma delimited params into arrays? Default is * true. * * Returns: * {Object} An object of key/value pairs from the query string. */ OpenLayers.Util.getParameters = function(url, options) { options = options || {}; // if no url specified, take it from the location bar url = (url === null || url === undefined) ? window.location.href : url; //parse out parameters portion of url string var paramsString = ""; if (OpenLayers.String.contains(url, '?')) { var start = url.indexOf('?') + 1; var end = OpenLayers.String.contains(url, "#") ? url.indexOf('#') : url.length; paramsString = url.substring(start, end); } var parameters = {}; var pairs = paramsString.split(/[&;]/); for(var i=0, len=pairs.length; i 1.0) ? (1.0 / scale) : scale; return normScale; }; /** * Function: getResolutionFromScale * * Parameters: * scale - {Float} * units - {String} Index into OpenLayers.INCHES_PER_UNIT hashtable. * Default is degrees * * Returns: * {Float} The corresponding resolution given passed-in scale and unit * parameters. If the given scale is falsey, the returned resolution will * be undefined. */ OpenLayers.Util.getResolutionFromScale = function (scale, units) { var resolution; if (scale) { if (units == null) { units = "degrees"; } var normScale = OpenLayers.Util.normalizeScale(scale); resolution = 1 / (normScale * OpenLayers.INCHES_PER_UNIT[units] * OpenLayers.DOTS_PER_INCH); } return resolution; }; /** * Function: getScaleFromResolution * * Parameters: * resolution - {Float} * units - {String} Index into OpenLayers.INCHES_PER_UNIT hashtable. * Default is degrees * * Returns: * {Float} The corresponding scale given passed-in resolution and unit * parameters. */ OpenLayers.Util.getScaleFromResolution = function (resolution, units) { if (units == null) { units = "degrees"; } var scale = resolution * OpenLayers.INCHES_PER_UNIT[units] * OpenLayers.DOTS_PER_INCH; return scale; }; /** * Function: pagePosition * Calculates the position of an element on the page (see * http://code.google.com/p/doctype/wiki/ArticlePageOffset) * * OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is * Copyright (c) 2006, Yahoo! Inc. * All rights reserved. * * Redistribution and use of this software in source and binary forms, with or * without modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * * Neither the name of Yahoo! Inc. nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission of Yahoo! Inc. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * Parameters: * forElement - {DOMElement} * * Returns: * {Array} two item array, Left value then Top value. */ OpenLayers.Util.pagePosition = function(forElement) { // NOTE: If element is hidden (display none or disconnected or any the // ancestors are hidden) we get (0,0) by default but we still do the // accumulation of scroll position. var pos = [0, 0]; var viewportElement = OpenLayers.Util.getViewportElement(); if (!forElement || forElement == window || forElement == viewportElement) { // viewport is always at 0,0 as that defined the coordinate system for // this function - this avoids special case checks in the code below return pos; } // Gecko browsers normally use getBoxObjectFor to calculate the position. // When invoked for an element with an implicit absolute position though it // can be off by one. Therefore the recursive implementation is used in // those (relatively rare) cases. var BUGGY_GECKO_BOX_OBJECT = OpenLayers.IS_GECKO && document.getBoxObjectFor && OpenLayers.Element.getStyle(forElement, 'position') == 'absolute' && (forElement.style.top == '' || forElement.style.left == ''); var parent = null; var box; if (forElement.getBoundingClientRect) { // IE box = forElement.getBoundingClientRect(); var scrollTop = window.pageYOffset || viewportElement.scrollTop; var scrollLeft = window.pageXOffset || viewportElement.scrollLeft; pos[0] = box.left + scrollLeft; pos[1] = box.top + scrollTop; } else if (document.getBoxObjectFor && !BUGGY_GECKO_BOX_OBJECT) { // gecko // Gecko ignores the scroll values for ancestors, up to 1.9. See: // https://bugzilla.mozilla.org/show_bug.cgi?id=328881 and // https://bugzilla.mozilla.org/show_bug.cgi?id=330619 box = document.getBoxObjectFor(forElement); var vpBox = document.getBoxObjectFor(viewportElement); pos[0] = box.screenX - vpBox.screenX; pos[1] = box.screenY - vpBox.screenY; } else { // safari/opera pos[0] = forElement.offsetLeft; pos[1] = forElement.offsetTop; parent = forElement.offsetParent; if (parent != forElement) { while (parent) { pos[0] += parent.offsetLeft; pos[1] += parent.offsetTop; parent = parent.offsetParent; } } var browser = OpenLayers.BROWSER_NAME; // opera & (safari absolute) incorrectly account for body offsetTop if (browser == "opera" || (browser == "safari" && OpenLayers.Element.getStyle(forElement, 'position') == 'absolute')) { pos[1] -= document.body.offsetTop; } // accumulate the scroll positions for everything but the body element parent = forElement.offsetParent; while (parent && parent != document.body) { pos[0] -= parent.scrollLeft; // see https://bugs.opera.com/show_bug.cgi?id=249965 if (browser != "opera" || parent.tagName != 'TR') { pos[1] -= parent.scrollTop; } parent = parent.offsetParent; } } return pos; }; /** * Function: getViewportElement * Returns die viewport element of the document. The viewport element is * usually document.documentElement, except in IE,where it is either * document.body or document.documentElement, depending on the document's * compatibility mode (see * http://code.google.com/p/doctype/wiki/ArticleClientViewportElement) * * Returns: * {DOMElement} */ OpenLayers.Util.getViewportElement = function() { var viewportElement = arguments.callee.viewportElement; if (viewportElement == undefined) { viewportElement = (OpenLayers.BROWSER_NAME == "msie" && document.compatMode != 'CSS1Compat') ? document.body : document.documentElement; arguments.callee.viewportElement = viewportElement; } return viewportElement; }; /** * Function: isEquivalentUrl * Test two URLs for equivalence. * * Setting 'ignoreCase' allows for case-independent comparison. * * Comparison is based on: * - Protocol * - Host (evaluated without the port) * - Port (set 'ignorePort80' to ignore "80" values) * - Hash ( set 'ignoreHash' to disable) * - Pathname (for relative <-> absolute comparison) * - Arguments (so they can be out of order) * * Parameters: * url1 - {String} * url2 - {String} * options - {Object} Allows for customization of comparison: * 'ignoreCase' - Default is True * 'ignorePort80' - Default is True * 'ignoreHash' - Default is True * * Returns: * {Boolean} Whether or not the two URLs are equivalent */ OpenLayers.Util.isEquivalentUrl = function(url1, url2, options) { options = options || {}; OpenLayers.Util.applyDefaults(options, { ignoreCase: true, ignorePort80: true, ignoreHash: true, splitArgs: false }); var urlObj1 = OpenLayers.Util.createUrlObject(url1, options); var urlObj2 = OpenLayers.Util.createUrlObject(url2, options); //compare all keys except for "args" (treated below) for(var key in urlObj1) { if(key !== "args") { if(urlObj1[key] != urlObj2[key]) { return false; } } } // compare search args - irrespective of order for(var key in urlObj1.args) { if(urlObj1.args[key] != urlObj2.args[key]) { return false; } delete urlObj2.args[key]; } // urlObj2 shouldn't have any args left for(var key in urlObj2.args) { return false; } return true; }; /** * Function: createUrlObject * * Parameters: * url - {String} * options - {Object} A hash of options. * * Valid options: * ignoreCase - {Boolean} lowercase url, * ignorePort80 - {Boolean} don't include explicit port if port is 80, * ignoreHash - {Boolean} Don't include part of url after the hash (#). * splitArgs - {Boolean} Split comma delimited params into arrays? Default is * true. * * Returns: * {Object} An object with separate url, a, port, host, and args parsed out * and ready for comparison */ OpenLayers.Util.createUrlObject = function(url, options) { options = options || {}; // deal with relative urls first if(!(/^\w+:\/\//).test(url)) { var loc = window.location; var port = loc.port ? ":" + loc.port : ""; var fullUrl = loc.protocol + "//" + loc.host.split(":").shift() + port; if(url.indexOf("/") === 0) { // full pathname url = fullUrl + url; } else { // relative to current path var parts = loc.pathname.split("/"); parts.pop(); url = fullUrl + parts.join("/") + "/" + url; } } if (options.ignoreCase) { url = url.toLowerCase(); } var a = document.createElement('a'); a.href = url; var urlObject = {}; //host (without port) urlObject.host = a.host.split(":").shift(); //protocol urlObject.protocol = a.protocol; //port (get uniform browser behavior with port 80 here) if(options.ignorePort80) { urlObject.port = (a.port == "80" || a.port == "0") ? "" : a.port; } else { urlObject.port = (a.port == "" || a.port == "0") ? "80" : a.port; } //hash urlObject.hash = (options.ignoreHash || a.hash === "#") ? "" : a.hash; //args var queryString = a.search; if (!queryString) { var qMark = url.indexOf("?"); queryString = (qMark != -1) ? url.substr(qMark) : ""; } urlObject.args = OpenLayers.Util.getParameters(queryString, {splitArgs: options.splitArgs}); // pathname // // This is a workaround for Internet Explorer where // window.location.pathname has a leading "/", but // a.pathname has no leading "/". urlObject.pathname = (a.pathname.charAt(0) == "/") ? a.pathname : "/" + a.pathname; return urlObject; }; /** * Function: removeTail * Takes a url and removes everything after the ? and # * * Parameters: * url - {String} The url to process * * Returns: * {String} The string with all queryString and Hash removed */ OpenLayers.Util.removeTail = function(url) { var head = null; var qMark = url.indexOf("?"); var hashMark = url.indexOf("#"); if (qMark == -1) { head = (hashMark != -1) ? url.substr(0,hashMark) : url; } else { head = (hashMark != -1) ? url.substr(0,Math.min(qMark, hashMark)) : url.substr(0, qMark); } return head; }; /** * Constant: IS_GECKO * {Boolean} True if the userAgent reports the browser to use the Gecko engine */ OpenLayers.IS_GECKO = (function() { var ua = navigator.userAgent.toLowerCase(); return ua.indexOf("webkit") == -1 && ua.indexOf("gecko") != -1; })(); /** * Constant: CANVAS_SUPPORTED * {Boolean} True if canvas 2d is supported. */ OpenLayers.CANVAS_SUPPORTED = (function() { var elem = document.createElement('canvas'); return !!(elem.getContext && elem.getContext('2d')); })(); /** * Constant: BROWSER_NAME * {String} * A substring of the navigator.userAgent property. Depending on the userAgent * property, this will be the empty string or one of the following: * * "opera" -- Opera * * "msie" -- Internet Explorer * * "safari" -- Safari * * "firefox" -- Firefox * * "mozilla" -- Mozilla */ OpenLayers.BROWSER_NAME = (function() { var name = ""; var ua = navigator.userAgent.toLowerCase(); if (ua.indexOf("opera") != -1) { name = "opera"; } else if (ua.indexOf("msie") != -1) { name = "msie"; } else if (ua.indexOf("safari") != -1) { name = "safari"; } else if (ua.indexOf("mozilla") != -1) { if (ua.indexOf("firefox") != -1) { name = "firefox"; } else { name = "mozilla"; } } return name; })(); /** * Function: getBrowserName * * Returns: * {String} A string which specifies which is the current * browser in which we are running. * * Currently-supported browser detection and codes: * * 'opera' -- Opera * * 'msie' -- Internet Explorer * * 'safari' -- Safari * * 'firefox' -- Firefox * * 'mozilla' -- Mozilla * * If we are unable to property identify the browser, we * return an empty string. */ OpenLayers.Util.getBrowserName = function() { return OpenLayers.BROWSER_NAME; }; /** * Method: getRenderedDimensions * Renders the contentHTML offscreen to determine actual dimensions for * popup sizing. As we need layout to determine dimensions the content * is rendered -9999px to the left and absolute to ensure the * scrollbars do not flicker * * Parameters: * contentHTML * size - {} If either the 'w' or 'h' properties is * specified, we fix that dimension of the div to be measured. This is * useful in the case where we have a limit in one dimension and must * therefore meaure the flow in the other dimension. * options - {Object} * * Allowed Options: * displayClass - {String} Optional parameter. A CSS class name(s) string * to provide the CSS context of the rendered content. * containerElement - {DOMElement} Optional parameter. Insert the HTML to * this node instead of the body root when calculating dimensions. * * Returns: * {} */ OpenLayers.Util.getRenderedDimensions = function(contentHTML, size, options) { var w, h; // create temp container div with restricted size var container = document.createElement("div"); container.style.visibility = "hidden"; var containerElement = (options && options.containerElement) ? options.containerElement : document.body; // Opera and IE7 can't handle a node with position:aboslute if it inherits // position:absolute from a parent. var parentHasPositionAbsolute = false; var superContainer = null; var parent = containerElement; while (parent && parent.tagName.toLowerCase()!="body") { var parentPosition = OpenLayers.Element.getStyle(parent, "position"); if(parentPosition == "absolute") { parentHasPositionAbsolute = true; break; } else if (parentPosition && parentPosition != "static") { break; } parent = parent.parentNode; } if(parentHasPositionAbsolute && (containerElement.clientHeight === 0 || containerElement.clientWidth === 0) ){ superContainer = document.createElement("div"); superContainer.style.visibility = "hidden"; superContainer.style.position = "absolute"; superContainer.style.overflow = "visible"; superContainer.style.width = document.body.clientWidth + "px"; superContainer.style.height = document.body.clientHeight + "px"; superContainer.appendChild(container); } container.style.position = "absolute"; //fix a dimension, if specified. if (size) { if (size.w) { w = size.w; container.style.width = w + "px"; } else if (size.h) { h = size.h; container.style.height = h + "px"; } } //add css classes, if specified if (options && options.displayClass) { container.className = options.displayClass; } // create temp content div and assign content var content = document.createElement("div"); content.innerHTML = contentHTML; // we need overflow visible when calculating the size content.style.overflow = "visible"; if (content.childNodes) { for (var i=0, l=content.childNodes.length; i= 60) { coordinateseconds -= 60; coordinateminutes += 1; if( coordinateminutes >= 60) { coordinateminutes -= 60; coordinatedegrees += 1; } } if( coordinatedegrees < 10 ) { coordinatedegrees = "0" + coordinatedegrees; } var str = coordinatedegrees + "\u00B0"; if (dmsOption.indexOf('dm') >= 0) { if( coordinateminutes < 10 ) { coordinateminutes = "0" + coordinateminutes; } str += coordinateminutes + "'"; if (dmsOption.indexOf('dms') >= 0) { if( coordinateseconds < 10 ) { coordinateseconds = "0" + coordinateseconds; } str += coordinateseconds + '"'; } } if (axis == "lon") { str += coordinate < 0 ? OpenLayers.i18n("W") : OpenLayers.i18n("E"); } else { str += coordinate < 0 ? OpenLayers.i18n("S") : OpenLayers.i18n("N"); } return str; }; /* ====================================================================== OpenLayers/Events.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Util.js */ /** * Namespace: OpenLayers.Event * Utility functions for event handling. */ OpenLayers.Event = { /** * Property: observers * {Object} A hashtable cache of the event observers. Keyed by * element._eventCacheID */ observers: false, /** * Constant: KEY_SPACE * {int} */ KEY_SPACE: 32, /** * Constant: KEY_BACKSPACE * {int} */ KEY_BACKSPACE: 8, /** * Constant: KEY_TAB * {int} */ KEY_TAB: 9, /** * Constant: KEY_RETURN * {int} */ KEY_RETURN: 13, /** * Constant: KEY_ESC * {int} */ KEY_ESC: 27, /** * Constant: KEY_LEFT * {int} */ KEY_LEFT: 37, /** * Constant: KEY_UP * {int} */ KEY_UP: 38, /** * Constant: KEY_RIGHT * {int} */ KEY_RIGHT: 39, /** * Constant: KEY_DOWN * {int} */ KEY_DOWN: 40, /** * Constant: KEY_DELETE * {int} */ KEY_DELETE: 46, /** * Method: element * Cross browser event element detection. * * Parameters: * event - {Event} * * Returns: * {DOMElement} The element that caused the event */ element: function(event) { return event.target || event.srcElement; }, /** * Method: isSingleTouch * Determine whether event was caused by a single touch * * Parameters: * event - {Event} * * Returns: * {Boolean} */ isSingleTouch: function(event) { return event.touches && event.touches.length == 1; }, /** * Method: isMultiTouch * Determine whether event was caused by a multi touch * * Parameters: * event - {Event} * * Returns: * {Boolean} */ isMultiTouch: function(event) { return event.touches && event.touches.length > 1; }, /** * Method: isLeftClick * Determine whether event was caused by a left click. * * Parameters: * event - {Event} * * Returns: * {Boolean} */ isLeftClick: function(event) { return (((event.which) && (event.which == 1)) || ((event.button) && (event.button == 1))); }, /** * Method: isRightClick * Determine whether event was caused by a right mouse click. * * Parameters: * event - {Event} * * Returns: * {Boolean} */ isRightClick: function(event) { return (((event.which) && (event.which == 3)) || ((event.button) && (event.button == 2))); }, /** * Method: stop * Stops an event from propagating. * * Parameters: * event - {Event} * allowDefault - {Boolean} If true, we stop the event chain but * still allow the default browser behaviour (text selection, * radio-button clicking, etc). Default is false. */ stop: function(event, allowDefault) { if (!allowDefault) { OpenLayers.Event.preventDefault(event); } if (event.stopPropagation) { event.stopPropagation(); } else { event.cancelBubble = true; } }, /** * Method: preventDefault * Cancels the event if it is cancelable, without stopping further * propagation of the event. * * Parameters: * event - {Event} */ preventDefault: function(event) { if (event.preventDefault) { event.preventDefault(); } else { event.returnValue = false; } }, /** * Method: findElement * * Parameters: * event - {Event} * tagName - {String} * * Returns: * {DOMElement} The first node with the given tagName, starting from the * node the event was triggered on and traversing the DOM upwards */ findElement: function(event, tagName) { var element = OpenLayers.Event.element(event); while (element.parentNode && (!element.tagName || (element.tagName.toUpperCase() != tagName.toUpperCase()))){ element = element.parentNode; } return element; }, /** * Method: observe * * Parameters: * elementParam - {DOMElement || String} * name - {String} * observer - {function} * useCapture - {Boolean} */ observe: function(elementParam, name, observer, useCapture) { var element = OpenLayers.Util.getElement(elementParam); useCapture = useCapture || false; if (name == 'keypress' && (navigator.appVersion.match(/Konqueror|Safari|KHTML/) || element.attachEvent)) { name = 'keydown'; } //if observers cache has not yet been created, create it if (!this.observers) { this.observers = {}; } //if not already assigned, make a new unique cache ID if (!element._eventCacheID) { var idPrefix = "eventCacheID_"; if (element.id) { idPrefix = element.id + "_" + idPrefix; } element._eventCacheID = OpenLayers.Util.createUniqueID(idPrefix); } var cacheID = element._eventCacheID; //if there is not yet a hash entry for this element, add one if (!this.observers[cacheID]) { this.observers[cacheID] = []; } //add a new observer to this element's list this.observers[cacheID].push({ 'element': element, 'name': name, 'observer': observer, 'useCapture': useCapture }); //add the actual browser event listener if (element.addEventListener) { element.addEventListener(name, observer, useCapture); } else if (element.attachEvent) { element.attachEvent('on' + name, observer); } }, /** * Method: stopObservingElement * Given the id of an element to stop observing, cycle through the * element's cached observers, calling stopObserving on each one, * skipping those entries which can no longer be removed. * * parameters: * elementParam - {DOMElement || String} */ stopObservingElement: function(elementParam) { var element = OpenLayers.Util.getElement(elementParam); var cacheID = element._eventCacheID; this._removeElementObservers(OpenLayers.Event.observers[cacheID]); }, /** * Method: _removeElementObservers * * Parameters: * elementObservers - {Array(Object)} Array of (element, name, * observer, usecapture) objects, * taken directly from hashtable */ _removeElementObservers: function(elementObservers) { if (elementObservers) { for(var i = elementObservers.length-1; i >= 0; i--) { var entry = elementObservers[i]; OpenLayers.Event.stopObserving.apply(this, [ entry.element, entry.name, entry.observer, entry.useCapture ]); } } }, /** * Method: stopObserving * * Parameters: * elementParam - {DOMElement || String} * name - {String} * observer - {function} * useCapture - {Boolean} * * Returns: * {Boolean} Whether or not the event observer was removed */ stopObserving: function(elementParam, name, observer, useCapture) { useCapture = useCapture || false; var element = OpenLayers.Util.getElement(elementParam); var cacheID = element._eventCacheID; if (name == 'keypress') { if ( navigator.appVersion.match(/Konqueror|Safari|KHTML/) || element.detachEvent) { name = 'keydown'; } } // find element's entry in this.observers cache and remove it var foundEntry = false; var elementObservers = OpenLayers.Event.observers[cacheID]; if (elementObservers) { // find the specific event type in the element's list var i=0; while(!foundEntry && i < elementObservers.length) { var cacheEntry = elementObservers[i]; if ((cacheEntry.name == name) && (cacheEntry.observer == observer) && (cacheEntry.useCapture == useCapture)) { elementObservers.splice(i, 1); if (elementObservers.length == 0) { delete OpenLayers.Event.observers[cacheID]; } foundEntry = true; break; } i++; } } //actually remove the event listener from browser if (foundEntry) { if (element.removeEventListener) { element.removeEventListener(name, observer, useCapture); } else if (element && element.detachEvent) { element.detachEvent('on' + name, observer); } } return foundEntry; }, /** * Method: unloadCache * Cycle through all the element entries in the events cache and call * stopObservingElement on each. */ unloadCache: function() { // check for OpenLayers.Event before checking for observers, because // OpenLayers.Event may be undefined in IE if no map instance was // created if (OpenLayers.Event && OpenLayers.Event.observers) { for (var cacheID in OpenLayers.Event.observers) { var elementObservers = OpenLayers.Event.observers[cacheID]; OpenLayers.Event._removeElementObservers.apply(this, [elementObservers]); } OpenLayers.Event.observers = false; } }, CLASS_NAME: "OpenLayers.Event" }; /* prevent memory leaks in IE */ OpenLayers.Event.observe(window, 'unload', OpenLayers.Event.unloadCache, false); /** * Class: OpenLayers.Events */ OpenLayers.Events = OpenLayers.Class({ /** * Constant: BROWSER_EVENTS * {Array(String)} supported events */ BROWSER_EVENTS: [ "mouseover", "mouseout", "mousedown", "mouseup", "mousemove", "click", "dblclick", "rightclick", "dblrightclick", "resize", "focus", "blur", "touchstart", "touchmove", "touchend", "keydown" ], /** * Property: listeners * {Object} Hashtable of Array(Function): events listener functions */ listeners: null, /** * Property: object * {Object} the code object issuing application events */ object: null, /** * Property: element * {DOMElement} the DOM element receiving browser events */ element: null, /** * Property: eventHandler * {Function} bound event handler attached to elements */ eventHandler: null, /** * APIProperty: fallThrough * {Boolean} */ fallThrough: null, /** * APIProperty: includeXY * {Boolean} Should the .xy property automatically be created for browser * mouse events? In general, this should be false. If it is true, then * mouse events will automatically generate a '.xy' property on the * event object that is passed. (Prior to OpenLayers 2.7, this was true * by default.) Otherwise, you can call the getMousePosition on the * relevant events handler on the object available via the 'evt.object' * property of the evt object. So, for most events, you can call: * function named(evt) { * this.xy = this.object.events.getMousePosition(evt) * } * * This option typically defaults to false for performance reasons: * when creating an events object whose primary purpose is to manage * relatively positioned mouse events within a div, it may make * sense to set it to true. * * This option is also used to control whether the events object caches * offsets. If this is false, it will not: the reason for this is that * it is only expected to be called many times if the includeXY property * is set to true. If you set this to true, you are expected to clear * the offset cache manually (using this.clearMouseCache()) if: * the border of the element changes * the location of the element in the page changes */ includeXY: false, /** * APIProperty: extensions * {Object} Event extensions registered with this instance. Keys are * event types, values are {OpenLayers.Events.*} extension instances or * {Boolean} for events that an instantiated extension provides in * addition to the one it was created for. * * Extensions create an event in addition to browser events, which usually * fires when a sequence of browser events is completed. Extensions are * automatically instantiated when a listener is registered for an event * provided by an extension. * * Extensions are created in the namespace using * , and named after the event they provide. * The constructor receives the target instance as * argument. Extensions that need to capture browser events before they * propagate can register their listeners events using , with * {extension: true} as 4th argument. * * If an extension creates more than one event, an alias for each event * type should be created and reference the same class. The constructor * should set a reference in the target's extensions registry to itself. * * Below is a minimal extension that provides the "foostart" and "fooend" * event types, which replace the native "click" event type if clicked on * an element with the css class "foo": * * (code) * OpenLayers.Events.foostart = OpenLayers.Class({ * initialize: function(target) { * this.target = target; * this.target.register("click", this, this.doStuff, {extension: true}); * // only required if extension provides more than one event type * this.target.extensions["foostart"] = true; * this.target.extensions["fooend"] = true; * }, * destroy: function() { * var target = this.target; * target.unregister("click", this, this.doStuff); * delete this.target; * // only required if extension provides more than one event type * delete target.extensions["foostart"]; * delete target.extensions["fooend"]; * }, * doStuff: function(evt) { * var propagate = true; * if (OpenLayers.Event.element(evt).className === "foo") { * propagate = false; * var target = this.target; * target.triggerEvent("foostart"); * window.setTimeout(function() { * target.triggerEvent("fooend"); * }, 1000); * } * return propagate; * } * }); * // only required if extension provides more than one event type * OpenLayers.Events.fooend = OpenLayers.Events.foostart; * (end) * */ extensions: null, /** * Property: extensionCount * {Object} Keys are event types (like in ), values are the * number of extension listeners for each event type. */ extensionCount: null, /** * Method: clearMouseListener * A version of that is bound to this instance so that * it can be used with and * . */ clearMouseListener: null, /** * Constructor: OpenLayers.Events * Construct an OpenLayers.Events object. * * Parameters: * object - {Object} The js object to which this Events object is being added * element - {DOMElement} A dom element to respond to browser events * eventTypes - {Array(String)} Deprecated. Array of custom application * events. A listener may be registered for any named event, regardless * of the values provided here. * fallThrough - {Boolean} Allow events to fall through after these have * been handled? * options - {Object} Options for the events object. */ initialize: function (object, element, eventTypes, fallThrough, options) { OpenLayers.Util.extend(this, options); this.object = object; this.fallThrough = fallThrough; this.listeners = {}; this.extensions = {}; this.extensionCount = {}; this._msTouches = []; // if a dom element is specified, add a listeners list // for browser events on the element and register them if (element != null) { this.attachToElement(element); } }, /** * APIMethod: destroy */ destroy: function () { for (var e in this.extensions) { if (typeof this.extensions[e] !== "boolean") { this.extensions[e].destroy(); } } this.extensions = null; if (this.element) { OpenLayers.Event.stopObservingElement(this.element); if(this.element.hasScrollEvent) { OpenLayers.Event.stopObserving( window, "scroll", this.clearMouseListener ); } } this.element = null; this.listeners = null; this.object = null; this.fallThrough = null; this.eventHandler = null; }, /** * APIMethod: addEventType * Deprecated. Any event can be triggered without adding it first. * * Parameters: * eventName - {String} */ addEventType: function(eventName) { }, /** * Method: attachToElement * * Parameters: * element - {HTMLDOMElement} a DOM element to attach browser events to */ attachToElement: function (element) { if (this.element) { OpenLayers.Event.stopObservingElement(this.element); } else { // keep a bound copy of handleBrowserEvent() so that we can // pass the same function to both Event.observe() and .stopObserving() this.eventHandler = OpenLayers.Function.bindAsEventListener( this.handleBrowserEvent, this ); // to be used with observe and stopObserving this.clearMouseListener = OpenLayers.Function.bind( this.clearMouseCache, this ); } this.element = element; var msTouch = !!window.navigator.msMaxTouchPoints; var type; for (var i = 0, len = this.BROWSER_EVENTS.length; i < len; i++) { type = this.BROWSER_EVENTS[i]; // register the event cross-browser OpenLayers.Event.observe(element, type, this.eventHandler ); if (msTouch && type.indexOf('touch') === 0) { this.addMsTouchListener(element, type, this.eventHandler); } } // disable dragstart in IE so that mousedown/move/up works normally OpenLayers.Event.observe(element, "dragstart", OpenLayers.Event.stop); }, /** * APIMethod: on * Convenience method for registering listeners with a common scope. * Internally, this method calls as shown in the examples * below. * * Example use: * (code) * // register a single listener for the "loadstart" event * events.on({"loadstart": loadStartListener}); * * // this is equivalent to the following * events.register("loadstart", undefined, loadStartListener); * * // register multiple listeners to be called with the same `this` object * events.on({ * "loadstart": loadStartListener, * "loadend": loadEndListener, * scope: object * }); * * // this is equivalent to the following * events.register("loadstart", object, loadStartListener); * events.register("loadend", object, loadEndListener); * (end) * * Parameters: * object - {Object} */ on: function(object) { for(var type in object) { if(type != "scope" && object.hasOwnProperty(type)) { this.register(type, object.scope, object[type]); } } }, /** * APIMethod: register * Register an event on the events object. * * When the event is triggered, the 'func' function will be called, in the * context of 'obj'. Imagine we were to register an event, specifying an * OpenLayers.Bounds Object as 'obj'. When the event is triggered, the * context in the callback function will be our Bounds object. This means * that within our callback function, we can access the properties and * methods of the Bounds object through the "this" variable. So our * callback could execute something like: * : leftStr = "Left: " + this.left; * * or * * : centerStr = "Center: " + this.getCenterLonLat(); * * Parameters: * type - {String} Name of the event to register * obj - {Object} The object to bind the context to for the callback#. * If no object is specified, default is the Events's 'object' property. * func - {Function} The callback function. If no callback is * specified, this function does nothing. * priority - {Boolean|Object} If true, adds the new listener to the * *front* of the events queue instead of to the end. * * Valid options for priority: * extension - {Boolean} If true, then the event will be registered as * extension event. Extension events are handled before all other * events. */ register: function (type, obj, func, priority) { if (type in OpenLayers.Events && !this.extensions[type]) { this.extensions[type] = new OpenLayers.Events[type](this); } if (func != null) { if (obj == null) { obj = this.object; } var listeners = this.listeners[type]; if (!listeners) { listeners = []; this.listeners[type] = listeners; this.extensionCount[type] = 0; } var listener = {obj: obj, func: func}; if (priority) { listeners.splice(this.extensionCount[type], 0, listener); if (typeof priority === "object" && priority.extension) { this.extensionCount[type]++; } } else { listeners.push(listener); } } }, /** * APIMethod: registerPriority * Same as register() but adds the new listener to the *front* of the * events queue instead of to the end. * * TODO: get rid of this in 3.0 - Decide whether listeners should be * called in the order they were registered or in reverse order. * * * Parameters: * type - {String} Name of the event to register * obj - {Object} The object to bind the context to for the callback#. * If no object is specified, default is the Events's * 'object' property. * func - {Function} The callback function. If no callback is * specified, this function does nothing. */ registerPriority: function (type, obj, func) { this.register(type, obj, func, true); }, /** * APIMethod: un * Convenience method for unregistering listeners with a common scope. * Internally, this method calls as shown in the examples * below. * * Example use: * (code) * // unregister a single listener for the "loadstart" event * events.un({"loadstart": loadStartListener}); * * // this is equivalent to the following * events.unregister("loadstart", undefined, loadStartListener); * * // unregister multiple listeners with the same `this` object * events.un({ * "loadstart": loadStartListener, * "loadend": loadEndListener, * scope: object * }); * * // this is equivalent to the following * events.unregister("loadstart", object, loadStartListener); * events.unregister("loadend", object, loadEndListener); * (end) */ un: function(object) { for(var type in object) { if(type != "scope" && object.hasOwnProperty(type)) { this.unregister(type, object.scope, object[type]); } } }, /** * APIMethod: unregister * * Parameters: * type - {String} * obj - {Object} If none specified, defaults to this.object * func - {Function} */ unregister: function (type, obj, func) { if (obj == null) { obj = this.object; } var listeners = this.listeners[type]; if (listeners != null) { for (var i=0, len=listeners.length; i Math.floor(evt.pageY) || evt.pageX === 0 && Math.floor(x) > Math.floor(evt.pageX)) { // iOS4 include scroll offset in clientX/Y x = x - winPageX; y = y - winPageY; } else if (y < (evt.pageY - winPageY) || x < (evt.pageX - winPageX) ) { // Some Android browsers have totally bogus values for clientX/Y // when scrolling/zooming a page x = evt.pageX - winPageX; y = evt.pageY - winPageY; } evt.olClientX = x; evt.olClientY = y; return { clientX: x, clientY: y }; }, /** * APIMethod: clearMouseCache * Clear cached data about the mouse position. This should be called any * time the element that events are registered on changes position * within the page. */ clearMouseCache: function() { this.element.scrolls = null; this.element.lefttop = null; this.element.offsets = null; }, /** * Method: getMousePosition * * Parameters: * evt - {Event} * * Returns: * {} The current xy coordinate of the mouse, adjusted * for offsets */ getMousePosition: function (evt) { if (!this.includeXY) { this.clearMouseCache(); } else if (!this.element.hasScrollEvent) { OpenLayers.Event.observe(window, "scroll", this.clearMouseListener); this.element.hasScrollEvent = true; } if (!this.element.scrolls) { var viewportElement = OpenLayers.Util.getViewportElement(); this.element.scrolls = [ window.pageXOffset || viewportElement.scrollLeft, window.pageYOffset || viewportElement.scrollTop ]; } if (!this.element.lefttop) { this.element.lefttop = [ (document.documentElement.clientLeft || 0), (document.documentElement.clientTop || 0) ]; } if (!this.element.offsets) { this.element.offsets = OpenLayers.Util.pagePosition(this.element); } return new OpenLayers.Pixel( (evt.clientX + this.element.scrolls[0]) - this.element.offsets[0] - this.element.lefttop[0], (evt.clientY + this.element.scrolls[1]) - this.element.offsets[1] - this.element.lefttop[1] ); }, /** * Method: addMsTouchListener * * Parameters: * element - {DOMElement} The DOM element to register the listener on * type - {String} The event type * handler - {Function} the handler */ addMsTouchListener: function (element, type, handler) { var eventHandler = this.eventHandler; var touches = this._msTouches; function msHandler(evt) { handler(OpenLayers.Util.applyDefaults({ stopPropagation: function() { for (var i=touches.length-1; i>=0; --i) { touches[i].stopPropagation(); } }, preventDefault: function() { for (var i=touches.length-1; i>=0; --i) { touches[i].preventDefault(); } }, type: type }, evt)); } switch (type) { case 'touchstart': return this.addMsTouchListenerStart(element, type, msHandler); case 'touchend': return this.addMsTouchListenerEnd(element, type, msHandler); case 'touchmove': return this.addMsTouchListenerMove(element, type, msHandler); default: throw 'Unknown touch event type'; } }, /** * Method: addMsTouchListenerStart * * Parameters: * element - {DOMElement} The DOM element to register the listener on * type - {String} The event type * handler - {Function} the handler */ addMsTouchListenerStart: function(element, type, handler) { var touches = this._msTouches; var cb = function(e) { var alreadyInArray = false; for (var i=0, ii=touches.length; i when a button was * clicked. Buttons are detected by the "olButton" class. * * This event type makes sure that button clicks do not interfere with other * events that are registered on the same . * * Event types provided by this extension: * - *buttonclick* Triggered when a button is clicked. Listeners receive an * object with a *buttonElement* property referencing the dom element of * the clicked button, and an *buttonXY* property with the click position * relative to the button. */ OpenLayers.Events.buttonclick = OpenLayers.Class({ /** * Property: target * {} The events instance that the buttonclick event will * be triggered on. */ target: null, /** * Property: events * {Array} Events to observe and conditionally stop from propagating when * an element with the olButton class (or its olAlphaImg child) is * clicked. */ events: [ 'mousedown', 'mouseup', 'click', 'dblclick', 'touchstart', 'touchmove', 'touchend', 'keydown' ], /** * Property: startRegEx * {RegExp} Regular expression to test Event.type for events that start * a buttonclick sequence. */ startRegEx: /^mousedown|touchstart$/, /** * Property: cancelRegEx * {RegExp} Regular expression to test Event.type for events that cancel * a buttonclick sequence. */ cancelRegEx: /^touchmove$/, /** * Property: completeRegEx * {RegExp} Regular expression to test Event.type for events that complete * a buttonclick sequence. */ completeRegEx: /^mouseup|touchend$/, /** * Property: startEvt * {Event} The event that started the click sequence */ /** * Constructor: OpenLayers.Events.buttonclick * Construct a buttonclick event type. Applications are not supposed to * create instances of this class - they are created on demand by * instances. * * Parameters: * target - {} The events instance that the buttonclick * event will be triggered on. */ initialize: function(target) { this.target = target; for (var i=this.events.length-1; i>=0; --i) { this.target.register(this.events[i], this, this.buttonClick, { extension: true }); } }, /** * Method: destroy */ destroy: function() { for (var i=this.events.length-1; i>=0; --i) { this.target.unregister(this.events[i], this, this.buttonClick); } delete this.target; }, /** * Method: getPressedButton * Get the pressed button, if any. Returns undefined if no button * was pressed. * * Arguments: * element - {DOMElement} The event target. * * Returns: * {DOMElement} The button element, or undefined. */ getPressedButton: function(element) { var depth = 3, // limit the search depth button; do { if(OpenLayers.Element.hasClass(element, "olButton")) { // hit! button = element; break; } element = element.parentNode; } while(--depth > 0 && element); return button; }, /** * Method: ignore * Check for event target elements that should be ignored by OpenLayers. * * Parameters: * element - {DOMElement} The event target. */ ignore: function(element) { var depth = 3, ignore = false; do { if (element.nodeName.toLowerCase() === 'a') { ignore = true; break; } element = element.parentNode; } while (--depth > 0 && element); return ignore; }, /** * Method: buttonClick * Check if a button was clicked, and fire the buttonclick event * * Parameters: * evt - {Event} */ buttonClick: function(evt) { var propagate = true, element = OpenLayers.Event.element(evt); if (element && (OpenLayers.Event.isLeftClick(evt) || !~evt.type.indexOf("mouse"))) { // was a button pressed? var button = this.getPressedButton(element); if (button) { if (evt.type === "keydown") { switch (evt.keyCode) { case OpenLayers.Event.KEY_RETURN: case OpenLayers.Event.KEY_SPACE: this.target.triggerEvent("buttonclick", { buttonElement: button }); OpenLayers.Event.stop(evt); propagate = false; break; } } else if (this.startEvt) { if (this.completeRegEx.test(evt.type)) { var pos = OpenLayers.Util.pagePosition(button); var viewportElement = OpenLayers.Util.getViewportElement(); var scrollTop = window.pageYOffset || viewportElement.scrollTop; var scrollLeft = window.pageXOffset || viewportElement.scrollLeft; pos[0] = pos[0] - scrollLeft; pos[1] = pos[1] - scrollTop; this.target.triggerEvent("buttonclick", { buttonElement: button, buttonXY: { x: this.startEvt.clientX - pos[0], y: this.startEvt.clientY - pos[1] } }); } if (this.cancelRegEx.test(evt.type)) { delete this.startEvt; } OpenLayers.Event.stop(evt); propagate = false; } if (this.startRegEx.test(evt.type)) { this.startEvt = evt; OpenLayers.Event.stop(evt); propagate = false; } } else { propagate = !this.ignore(OpenLayers.Event.element(evt)); delete this.startEvt; } } return propagate; } }); /* ====================================================================== OpenLayers/Util/vendorPrefix.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/SingleFile.js */ OpenLayers.Util = OpenLayers.Util || {}; /** * Namespace: OpenLayers.Util.vendorPrefix * A collection of utility functions to detect vendor prefixed features */ OpenLayers.Util.vendorPrefix = (function() { "use strict"; var VENDOR_PREFIXES = ["", "O", "ms", "Moz", "Webkit"], divStyle = document.createElement("div").style, cssCache = {}, jsCache = {}; /** * Function: domToCss * Converts a upper camel case DOM style property name to a CSS property * i.e. transformOrigin -> transform-origin * or WebkitTransformOrigin -> -webkit-transform-origin * * Parameters: * prefixedDom - {String} The property to convert * * Returns: * {String} The CSS property */ function domToCss(prefixedDom) { if (!prefixedDom) { return null; } return prefixedDom. replace(/([A-Z])/g, function(c) { return "-" + c.toLowerCase(); }). replace(/^ms-/, "-ms-"); } /** * APIMethod: css * Detect which property is used for a CSS property * * Parameters: * property - {String} The standard (unprefixed) CSS property name * * Returns: * {String} The standard CSS property, prefixed property or null if not * supported */ function css(property) { if (cssCache[property] === undefined) { var domProperty = property. replace(/(-[\s\S])/g, function(c) { return c.charAt(1).toUpperCase(); }); var prefixedDom = style(domProperty); cssCache[property] = domToCss(prefixedDom); } return cssCache[property]; } /** * APIMethod: js * Detect which property is used for a JS property/method * * Parameters: * obj - {Object} The object to test on * property - {String} The standard (unprefixed) JS property name * * Returns: * {String} The standard JS property, prefixed property or null if not * supported */ function js(obj, property) { if (jsCache[property] === undefined) { var tmpProp, i = 0, l = VENDOR_PREFIXES.length, prefix, isStyleObj = (typeof obj.cssText !== "undefined"); jsCache[property] = null; for(; i in series for some * duration. * * Parameters: * callback - {Function} The function to be called at the next animation frame. * duration - {Number} Optional duration for the loop. If not provided, the * animation loop will execute indefinitely. * element - {DOMElement} Optional element that visually bounds the animation. * * Returns: * {Number} Identifier for the animation loop. Used to stop animations with * . */ function start(callback, duration, element) { duration = duration > 0 ? duration : Number.POSITIVE_INFINITY; var id = ++counter; var start = +new Date; loops[id] = function() { if (loops[id] && +new Date - start <= duration) { callback(); if (loops[id]) { requestFrame(loops[id], element); } } else { delete loops[id]; } }; requestFrame(loops[id], element); return id; } /** * Function: stop * Terminates an animation loop started with . * * Parameters: * id - {Number} Identifier returned from . */ function stop(id) { delete loops[id]; } return { isNative: isNative, requestFrame: requestFrame, start: start, stop: stop }; })(window); /* ====================================================================== OpenLayers/Tween.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/BaseTypes/Class.js * @requires OpenLayers/Animation.js */ /** * Namespace: OpenLayers.Tween */ OpenLayers.Tween = OpenLayers.Class({ /** * APIProperty: easing * {(Function)} Easing equation used for the animation * Defaultly set to OpenLayers.Easing.Expo.easeOut */ easing: null, /** * APIProperty: begin * {Object} Values to start the animation with */ begin: null, /** * APIProperty: finish * {Object} Values to finish the animation with */ finish: null, /** * APIProperty: duration * {int} duration of the tween (number of steps) */ duration: null, /** * APIProperty: callbacks * {Object} An object with start, eachStep and done properties whose values * are functions to be call during the animation. They are passed the * current computed value as argument. */ callbacks: null, /** * Property: time * {int} Step counter */ time: null, /** * APIProperty: minFrameRate * {Number} The minimum framerate for animations in frames per second. After * each step, the time spent in the animation is compared to the calculated * time at this frame rate. If the animation runs longer than the calculated * time, the next step is skipped. Default is 30. */ minFrameRate: null, /** * Property: startTime * {Number} The timestamp of the first execution step. Used for skipping * frames */ startTime: null, /** * Property: animationId * {int} Loop id returned by OpenLayers.Animation.start */ animationId: null, /** * Property: playing * {Boolean} Tells if the easing is currently playing */ playing: false, /** * Constructor: OpenLayers.Tween * Creates a Tween. * * Parameters: * easing - {(Function)} easing function method to use */ initialize: function(easing) { this.easing = (easing) ? easing : OpenLayers.Easing.Expo.easeOut; }, /** * APIMethod: start * Plays the Tween, and calls the callback method on each step * * Parameters: * begin - {Object} values to start the animation with * finish - {Object} values to finish the animation with * duration - {int} duration of the tween (number of steps) * options - {Object} hash of options (callbacks (start, eachStep, done), * minFrameRate) */ start: function(begin, finish, duration, options) { this.playing = true; this.begin = begin; this.finish = finish; this.duration = duration; this.callbacks = options.callbacks; this.minFrameRate = options.minFrameRate || 30; this.time = 0; this.startTime = new Date().getTime(); OpenLayers.Animation.stop(this.animationId); this.animationId = null; if (this.callbacks && this.callbacks.start) { this.callbacks.start.call(this, this.begin); } this.animationId = OpenLayers.Animation.start( OpenLayers.Function.bind(this.play, this) ); }, /** * APIMethod: stop * Stops the Tween, and calls the done callback * Doesn't do anything if animation is already finished */ stop: function() { if (!this.playing) { return; } if (this.callbacks && this.callbacks.done) { this.callbacks.done.call(this, this.finish); } OpenLayers.Animation.stop(this.animationId); this.animationId = null; this.playing = false; }, /** * Method: play * Calls the appropriate easing method */ play: function() { var value = {}; for (var i in this.begin) { var b = this.begin[i]; var f = this.finish[i]; if (b == null || f == null || isNaN(b) || isNaN(f)) { throw new TypeError('invalid value for Tween'); } var c = f - b; value[i] = this.easing.apply(this, [this.time, b, c, this.duration]); } this.time++; if (this.callbacks && this.callbacks.eachStep) { // skip frames if frame rate drops below threshold if ((new Date().getTime() - this.startTime) / this.time <= 1000 / this.minFrameRate) { this.callbacks.eachStep.call(this, value); } } if (this.time > this.duration) { this.stop(); } }, /** * Create empty functions for all easing methods. */ CLASS_NAME: "OpenLayers.Tween" }); /** * Namespace: OpenLayers.Easing * * Credits: * Easing Equations by Robert Penner, */ OpenLayers.Easing = { /** * Create empty functions for all easing methods. */ CLASS_NAME: "OpenLayers.Easing" }; /** * Namespace: OpenLayers.Easing.Linear */ OpenLayers.Easing.Linear = { /** * Function: easeIn * * Parameters: * t - {Float} time * b - {Float} beginning position * c - {Float} total change * d - {Float} duration of the transition * * Returns: * {Float} */ easeIn: function(t, b, c, d) { return c*t/d + b; }, /** * Function: easeOut * * Parameters: * t - {Float} time * b - {Float} beginning position * c - {Float} total change * d - {Float} duration of the transition * * Returns: * {Float} */ easeOut: function(t, b, c, d) { return c*t/d + b; }, /** * Function: easeInOut * * Parameters: * t - {Float} time * b - {Float} beginning position * c - {Float} total change * d - {Float} duration of the transition * * Returns: * {Float} */ easeInOut: function(t, b, c, d) { return c*t/d + b; }, CLASS_NAME: "OpenLayers.Easing.Linear" }; /** * Namespace: OpenLayers.Easing.Expo */ OpenLayers.Easing.Expo = { /** * Function: easeIn * * Parameters: * t - {Float} time * b - {Float} beginning position * c - {Float} total change * d - {Float} duration of the transition * * Returns: * {Float} */ easeIn: function(t, b, c, d) { return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b; }, /** * Function: easeOut * * Parameters: * t - {Float} time * b - {Float} beginning position * c - {Float} total change * d - {Float} duration of the transition * * Returns: * {Float} */ easeOut: function(t, b, c, d) { return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b; }, /** * Function: easeInOut * * Parameters: * t - {Float} time * b - {Float} beginning position * c - {Float} total change * d - {Float} duration of the transition * * Returns: * {Float} */ easeInOut: function(t, b, c, d) { if (t==0) return b; if (t==d) return b+c; if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b; return c/2 * (-Math.pow(2, -10 * --t) + 2) + b; }, CLASS_NAME: "OpenLayers.Easing.Expo" }; /** * Namespace: OpenLayers.Easing.Quad */ OpenLayers.Easing.Quad = { /** * Function: easeIn * * Parameters: * t - {Float} time * b - {Float} beginning position * c - {Float} total change * d - {Float} duration of the transition * * Returns: * {Float} */ easeIn: function(t, b, c, d) { return c*(t/=d)*t + b; }, /** * Function: easeOut * * Parameters: * t - {Float} time * b - {Float} beginning position * c - {Float} total change * d - {Float} duration of the transition * * Returns: * {Float} */ easeOut: function(t, b, c, d) { return -c *(t/=d)*(t-2) + b; }, /** * Function: easeInOut * * Parameters: * t - {Float} time * b - {Float} beginning position * c - {Float} total change * d - {Float} duration of the transition * * Returns: * {Float} */ easeInOut: function(t, b, c, d) { if ((t/=d/2) < 1) return c/2*t*t + b; return -c/2 * ((--t)*(t-2) - 1) + b; }, CLASS_NAME: "OpenLayers.Easing.Quad" }; /* ====================================================================== OpenLayers/Projection.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/BaseTypes/Class.js * @requires OpenLayers/Util.js */ /** * Namespace: OpenLayers.Projection * Methods for coordinate transforms between coordinate systems. By default, * OpenLayers ships with the ability to transform coordinates between * geographic (EPSG:4326) and web or spherical mercator (EPSG:900913 et al.) * coordinate reference systems. See the method for details * on usage. * * Additional transforms may be added by using the * library. If the proj4js library is included, the method * will work between any two coordinate reference systems with proj4js * definitions. * * If the proj4js library is not included, or if you wish to allow transforms * between arbitrary coordinate reference systems, use the * method to register a custom transform method. */ OpenLayers.Projection = OpenLayers.Class({ /** * Property: proj * {Object} Proj4js.Proj instance. */ proj: null, /** * Property: projCode * {String} */ projCode: null, /** * Property: titleRegEx * {RegExp} regular expression to strip the title from a proj4js definition */ titleRegEx: /\+title=[^\+]*/, /** * Constructor: OpenLayers.Projection * This class offers several methods for interacting with a wrapped * pro4js projection object. * * Parameters: * projCode - {String} A string identifying the Well Known Identifier for * the projection. * options - {Object} An optional object to set additional properties * on the projection. * * Returns: * {} A projection object. */ initialize: function(projCode, options) { OpenLayers.Util.extend(this, options); this.projCode = projCode; if (typeof Proj4js == "object") { this.proj = new Proj4js.Proj(projCode); } }, /** * APIMethod: getCode * Get the string SRS code. * * Returns: * {String} The SRS code. */ getCode: function() { return this.proj ? this.proj.srsCode : this.projCode; }, /** * APIMethod: getUnits * Get the units string for the projection -- returns null if * proj4js is not available. * * Returns: * {String} The units abbreviation. */ getUnits: function() { return this.proj ? this.proj.units : null; }, /** * Method: toString * Convert projection to string (getCode wrapper). * * Returns: * {String} The projection code. */ toString: function() { return this.getCode(); }, /** * Method: equals * Test equality of two projection instances. Determines equality based * soley on the projection code. * * Returns: * {Boolean} The two projections are equivalent. */ equals: function(projection) { var p = projection, equals = false; if (p) { if (!(p instanceof OpenLayers.Projection)) { p = new OpenLayers.Projection(p); } if ((typeof Proj4js == "object") && this.proj.defData && p.proj.defData) { equals = this.proj.defData.replace(this.titleRegEx, "") == p.proj.defData.replace(this.titleRegEx, ""); } else if (p.getCode) { var source = this.getCode(), target = p.getCode(); equals = source == target || !!OpenLayers.Projection.transforms[source] && OpenLayers.Projection.transforms[source][target] === OpenLayers.Projection.nullTransform; } } return equals; }, /* Method: destroy * Destroy projection object. */ destroy: function() { delete this.proj; delete this.projCode; }, CLASS_NAME: "OpenLayers.Projection" }); /** * Property: transforms * {Object} Transforms is an object, with from properties, each of which may * have a to property. This allows you to define projections without * requiring support for proj4js to be included. * * This object has keys which correspond to a 'source' projection object. The * keys should be strings, corresponding to the projection.getCode() value. * Each source projection object should have a set of destination projection * keys included in the object. * * Each value in the destination object should be a transformation function, * where the function is expected to be passed an object with a .x and a .y * property. The function should return the object, with the .x and .y * transformed according to the transformation function. * * Note - Properties on this object should not be set directly. To add a * transform method to this object, use the method. For an * example of usage, see the OpenLayers.Layer.SphericalMercator file. */ OpenLayers.Projection.transforms = {}; /** * APIProperty: defaults * {Object} Defaults for the SRS codes known to OpenLayers (currently * EPSG:4326, CRS:84, urn:ogc:def:crs:EPSG:6.6:4326, EPSG:900913, EPSG:3857, * EPSG:102113 and EPSG:102100). Keys are the SRS code, values are units, * maxExtent (the validity extent for the SRS) and yx (true if this SRS is * known to have a reverse axis order). */ OpenLayers.Projection.defaults = { "EPSG:4326": { units: "degrees", maxExtent: [-180, -90, 180, 90], yx: true }, "CRS:84": { units: "degrees", maxExtent: [-180, -90, 180, 90] }, "EPSG:900913": { units: "m", maxExtent: [-20037508.34, -20037508.34, 20037508.34, 20037508.34] } }; /** * APIMethod: addTransform * Set a custom transform method between two projections. Use this method in * cases where the proj4js lib is not available or where custom projections * need to be handled. * * Parameters: * from - {String} The code for the source projection * to - {String} the code for the destination projection * method - {Function} A function that takes a point as an argument and * transforms that point from the source to the destination projection * in place. The original point should be modified. */ OpenLayers.Projection.addTransform = function(from, to, method) { if (method === OpenLayers.Projection.nullTransform) { var defaults = OpenLayers.Projection.defaults[from]; if (defaults && !OpenLayers.Projection.defaults[to]) { OpenLayers.Projection.defaults[to] = defaults; } } if(!OpenLayers.Projection.transforms[from]) { OpenLayers.Projection.transforms[from] = {}; } OpenLayers.Projection.transforms[from][to] = method; }; /** * APIMethod: transform * Transform a point coordinate from one projection to another. Note that * the input point is transformed in place. * * Parameters: * point - { | Object} An object with x and y * properties representing coordinates in those dimensions. * source - {OpenLayers.Projection} Source map coordinate system * dest - {OpenLayers.Projection} Destination map coordinate system * * Returns: * point - {object} A transformed coordinate. The original point is modified. */ OpenLayers.Projection.transform = function(point, source, dest) { if (source && dest) { if (!(source instanceof OpenLayers.Projection)) { source = new OpenLayers.Projection(source); } if (!(dest instanceof OpenLayers.Projection)) { dest = new OpenLayers.Projection(dest); } if (source.proj && dest.proj) { point = Proj4js.transform(source.proj, dest.proj, point); } else { var sourceCode = source.getCode(); var destCode = dest.getCode(); var transforms = OpenLayers.Projection.transforms; if (transforms[sourceCode] && transforms[sourceCode][destCode]) { transforms[sourceCode][destCode](point); } } } return point; }; /** * APIFunction: nullTransform * A null transformation - useful for defining projection aliases when * proj4js is not available: * * (code) * OpenLayers.Projection.addTransform("EPSG:3857", "EPSG:900913", * OpenLayers.Projection.nullTransform); * OpenLayers.Projection.addTransform("EPSG:900913", "EPSG:3857", * OpenLayers.Projection.nullTransform); * (end) */ OpenLayers.Projection.nullTransform = function(point) { return point; }; /** * Note: Transforms for web mercator <-> geographic * OpenLayers recognizes EPSG:3857, EPSG:900913, EPSG:102113 and EPSG:102100. * OpenLayers originally started referring to EPSG:900913 as web mercator. * The EPSG has declared EPSG:3857 to be web mercator. * ArcGIS 10 recognizes the EPSG:3857, EPSG:102113, and EPSG:102100 as * equivalent. See http://blogs.esri.com/Dev/blogs/arcgisserver/archive/2009/11/20/ArcGIS-Online-moving-to-Google-_2F00_-Bing-tiling-scheme_3A00_-What-does-this-mean-for-you_3F00_.aspx#12084. * For geographic, OpenLayers recognizes EPSG:4326, CRS:84 and * urn:ogc:def:crs:EPSG:6.6:4326. OpenLayers also knows about the reverse axis * order for EPSG:4326. */ (function() { var pole = 20037508.34; function inverseMercator(xy) { xy.x = 180 * xy.x / pole; xy.y = 180 / Math.PI * (2 * Math.atan(Math.exp((xy.y / pole) * Math.PI)) - Math.PI / 2); return xy; } function forwardMercator(xy) { xy.x = xy.x * pole / 180; var y = Math.log(Math.tan((90 + xy.y) * Math.PI / 360)) / Math.PI * pole; xy.y = Math.max(-20037508.34, Math.min(y, 20037508.34)); return xy; } function map(base, codes) { var add = OpenLayers.Projection.addTransform; var same = OpenLayers.Projection.nullTransform; var i, len, code, other, j; for (i=0, len=codes.length; i=0; --i) { map(mercator[i], geographic); } for (i=geographic.length-1; i>=0; --i) { map(geographic[i], mercator); } })(); /* ====================================================================== OpenLayers/Map.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/BaseTypes/Class.js * @requires OpenLayers/Util.js * @requires OpenLayers/Util/vendorPrefix.js * @requires OpenLayers/Events.js * @requires OpenLayers/Tween.js * @requires OpenLayers/Projection.js */ /** * Class: OpenLayers.Map * Instances of OpenLayers.Map are interactive maps embedded in a web page. * Create a new map with the constructor. * * On their own maps do not provide much functionality. To extend a map * it's necessary to add controls () and * layers () to the map. */ OpenLayers.Map = OpenLayers.Class({ /** * Constant: Z_INDEX_BASE * {Object} Base z-indexes for different classes of thing */ Z_INDEX_BASE: { BaseLayer: 100, Overlay: 325, Feature: 725, Popup: 750, Control: 1000 }, /** * APIProperty: events * {} * * Register a listener for a particular event with the following syntax: * (code) * map.events.register(type, obj, listener); * (end) * * Listeners will be called with a reference to an event object. The * properties of this event depends on exactly what happened. * * All event objects have at least the following properties: * object - {Object} A reference to map.events.object. * element - {DOMElement} A reference to map.events.element. * * Browser events have the following additional properties: * xy - {} The pixel location of the event (relative * to the the map viewport). * * Supported map event types: * preaddlayer - triggered before a layer has been added. The event * object will include a *layer* property that references the layer * to be added. When a listener returns "false" the adding will be * aborted. * addlayer - triggered after a layer has been added. The event object * will include a *layer* property that references the added layer. * preremovelayer - triggered before a layer has been removed. The event * object will include a *layer* property that references the layer * to be removed. When a listener returns "false" the removal will be * aborted. * removelayer - triggered after a layer has been removed. The event * object will include a *layer* property that references the removed * layer. * changelayer - triggered after a layer name change, order change, * opacity change, params change, visibility change (actual visibility, * not the layer's visibility property) or attribution change (due to * extent change). Listeners will receive an event object with *layer* * and *property* properties. The *layer* property will be a reference * to the changed layer. The *property* property will be a key to the * changed property (name, order, opacity, params, visibility or * attribution). * movestart - triggered after the start of a drag, pan, or zoom. The event * object may include a *zoomChanged* property that tells whether the * zoom has changed. * move - triggered after each drag, pan, or zoom * moveend - triggered after a drag, pan, or zoom completes * zoomend - triggered after a zoom completes * mouseover - triggered after mouseover the map * mouseout - triggered after mouseout the map * mousemove - triggered after mousemove the map * changebaselayer - triggered after the base layer changes * updatesize - triggered after the method was executed */ /** * Property: id * {String} Unique identifier for the map */ id: null, /** * Property: fractionalZoom * {Boolean} For a base layer that supports it, allow the map resolution * to be set to a value between one of the values in the resolutions * array. Default is false. * * When fractionalZoom is set to true, it is possible to zoom to * an arbitrary extent. This requires a base layer from a source * that supports requests for arbitrary extents (i.e. not cached * tiles on a regular lattice). This means that fractionalZoom * will not work with commercial layers (Google, Yahoo, VE), layers * using TileCache, or any other pre-cached data sources. * * If you are using fractionalZoom, then you should also use * instead of layer.resolutions[zoom] as the * former works for non-integer zoom levels. */ fractionalZoom: false, /** * APIProperty: events * {} An events object that handles all * events on the map */ events: null, /** * APIProperty: allOverlays * {Boolean} Allow the map to function with "overlays" only. Defaults to * false. If true, the lowest layer in the draw order will act as * the base layer. In addition, if set to true, all layers will * have isBaseLayer set to false when they are added to the map. * * Note: * If you set map.allOverlays to true, then you *cannot* use * map.setBaseLayer or layer.setIsBaseLayer. With allOverlays true, * the lowest layer in the draw layer is the base layer. So, to change * the base layer, use or to set the layer * index to 0. */ allOverlays: false, /** * APIProperty: div * {DOMElement|String} The element that contains the map (or an id for * that element). If the constructor is called * with two arguments, this should be provided as the first argument. * Alternatively, the map constructor can be called with the options * object as the only argument. In this case (one argument), a * div property may or may not be provided. If the div property * is not provided, the map can be rendered to a container later * using the method. * * Note: * If you are calling after map construction, do not use * auto. Instead, divide your by your * maximum expected dimension. */ div: null, /** * Property: dragging * {Boolean} The map is currently being dragged. */ dragging: false, /** * Property: size * {} Size of the main div (this.div) */ size: null, /** * Property: viewPortDiv * {HTMLDivElement} The element that represents the map viewport */ viewPortDiv: null, /** * Property: layerContainerOrigin * {} The lonlat at which the later container was * re-initialized (on-zoom) */ layerContainerOrigin: null, /** * Property: layerContainerDiv * {HTMLDivElement} The element that contains the layers. */ layerContainerDiv: null, /** * APIProperty: layers * {Array()} Ordered list of layers in the map */ layers: null, /** * APIProperty: controls * {Array()} List of controls associated with the map. * * If not provided in the map options at construction, the map will * by default be given the following controls if present in the build: * - or * - or * - * - */ controls: null, /** * Property: popups * {Array()} List of popups associated with the map */ popups: null, /** * APIProperty: baseLayer * {} The currently selected base layer. This determines * min/max zoom level, projection, etc. */ baseLayer: null, /** * Property: center * {} The current center of the map */ center: null, /** * Property: resolution * {Float} The resolution of the map. */ resolution: null, /** * Property: zoom * {Integer} The current zoom level of the map */ zoom: 0, /** * Property: panRatio * {Float} The ratio of the current extent within * which panning will tween. */ panRatio: 1.5, /** * APIProperty: options * {Object} The options object passed to the class constructor. Read-only. */ options: null, // Options /** * APIProperty: tileSize * {} Set in the map options to override the default tile * size for this map. */ tileSize: null, /** * APIProperty: projection * {String} Set in the map options to specify the default projection * for layers added to this map. When using a projection other than EPSG:4326 * (CRS:84, Geographic) or EPSG:3857 (EPSG:900913, Web Mercator), * also set maxExtent, maxResolution or resolutions. Default is "EPSG:4326". * Note that the projection of the map is usually determined * by that of the current baseLayer (see and ). */ projection: "EPSG:4326", /** * APIProperty: units * {String} The map units. Possible values are 'degrees' (or 'dd'), 'm', * 'ft', 'km', 'mi', 'inches'. Normally taken from the projection. * Only required if both map and layers do not define a projection, * or if they define a projection which does not define units */ units: null, /** * APIProperty: resolutions * {Array(Float)} A list of map resolutions (map units per pixel) in * descending order. If this is not set in the layer constructor, it * will be set based on other resolution related properties * (maxExtent, maxResolution, maxScale, etc.). */ resolutions: null, /** * APIProperty: maxResolution * {Float} Required if you are not displaying the whole world on a tile * with the size specified in . */ maxResolution: null, /** * APIProperty: minResolution * {Float} */ minResolution: null, /** * APIProperty: maxScale * {Float} */ maxScale: null, /** * APIProperty: minScale * {Float} */ minScale: null, /** * APIProperty: maxExtent * {|Array} If provided as an array, the array * should consist of four values (left, bottom, right, top). * The maximum extent for the map. * Default depends on projection; if this is one of those defined in OpenLayers.Projection.defaults * (EPSG:4326 or web mercator), maxExtent will be set to the value defined there; * else, defaults to null. * To restrict user panning and zooming of the map, use instead. * The value for will change calculations for tile URLs. */ maxExtent: null, /** * APIProperty: minExtent * {|Array} If provided as an array, the array * should consist of four values (left, bottom, right, top). * The minimum extent for the map. Defaults to null. */ minExtent: null, /** * APIProperty: restrictedExtent * {|Array} If provided as an array, the array * should consist of four values (left, bottom, right, top). * Limit map navigation to this extent where possible. * If a non-null restrictedExtent is set, panning will be restricted * to the given bounds. In addition, zooming to a resolution that * displays more than the restricted extent will center the map * on the restricted extent. If you wish to limit the zoom level * or resolution, use maxResolution. */ restrictedExtent: null, /** * APIProperty: numZoomLevels * {Integer} Number of zoom levels for the map. Defaults to 16. Set a * different value in the map options if needed. */ numZoomLevels: 16, /** * APIProperty: theme * {String} Relative path to a CSS file from which to load theme styles. * Specify null in the map options (e.g. {theme: null}) if you * want to get cascading style declarations - by putting links to * stylesheets or style declarations directly in your page. */ theme: null, /** * APIProperty: displayProjection * {} Requires proj4js support for projections other * than EPSG:4326 or EPSG:900913/EPSG:3857. Projection used by * several controls to display data to user. If this property is set, * it will be set on any control which has a null displayProjection * property at the time the control is added to the map. */ displayProjection: null, /** * APIProperty: tileManager * {|Object} By default, and if the build contains * TileManager.js, the map will use the TileManager to queue image requests * and to cache tile image elements. To create a map without a TileManager * configure the map with tileManager: null. To create a TileManager with * non-default options, supply the options instead or alternatively supply * an instance of {}. */ /** * APIProperty: fallThrough * {Boolean} Should OpenLayers allow events on the map to fall through to * other elements on the page, or should it swallow them? (#457) * Default is to swallow. */ fallThrough: false, /** * APIProperty: autoUpdateSize * {Boolean} Should OpenLayers automatically update the size of the map * when the resize event is fired. Default is true. */ autoUpdateSize: true, /** * APIProperty: eventListeners * {Object} If set as an option at construction, the eventListeners * object will be registered with . Object * structure must be a listeners object as shown in the example for * the events.on method. */ eventListeners: null, /** * Property: panTween * {} Animated panning tween object, see panTo() */ panTween: null, /** * APIProperty: panMethod * {Function} The Easing function to be used for tweening. Default is * OpenLayers.Easing.Expo.easeOut. Setting this to 'null' turns off * animated panning. */ panMethod: OpenLayers.Easing.Expo.easeOut, /** * Property: panDuration * {Integer} The number of steps to be passed to the * OpenLayers.Tween.start() method when the map is * panned. * Default is 50. */ panDuration: 50, /** * Property: zoomTween * {} Animated zooming tween object, see zoomTo() */ zoomTween: null, /** * APIProperty: zoomMethod * {Function} The Easing function to be used for tweening. Default is * OpenLayers.Easing.Quad.easeOut. Setting this to 'null' turns off * animated zooming. */ zoomMethod: OpenLayers.Easing.Quad.easeOut, /** * Property: zoomDuration * {Integer} The number of steps to be passed to the * OpenLayers.Tween.start() method when the map is zoomed. * Default is 20. */ zoomDuration: 20, /** * Property: paddingForPopups * {} Outside margin of the popup. Used to prevent * the popup from getting too close to the map border. */ paddingForPopups : null, /** * Property: layerContainerOriginPx * {Object} Cached object representing the layer container origin (in pixels). */ layerContainerOriginPx: null, /** * Property: minPx * {Object} An object with a 'x' and 'y' values that is the lower * left of maxExtent in viewport pixel space. * Used to verify in moveByPx that the new location we're moving to * is valid. It is also used in the getLonLatFromViewPortPx function * of Layer. */ minPx: null, /** * Property: maxPx * {Object} An object with a 'x' and 'y' values that is the top * right of maxExtent in viewport pixel space. * Used to verify in moveByPx that the new location we're moving to * is valid. */ maxPx: null, /** * Constructor: OpenLayers.Map * Constructor for a new OpenLayers.Map instance. There are two possible * ways to call the map constructor. See the examples below. * * Parameters: * div - {DOMElement|String} The element or id of an element in your page * that will contain the map. May be omitted if the
option is * provided or if you intend to call the method later. * options - {Object} Optional object with properties to tag onto the map. * * Valid options (in addition to the listed API properties): * center - {|Array} The default initial center of the map. * If provided as array, the first value is the x coordinate, * and the 2nd value is the y coordinate. * Only specify if is provided. * Note that if an ArgParser/Permalink control is present, * and the querystring contains coordinates, center will be set * by that, and this option will be ignored. * zoom - {Number} The initial zoom level for the map. Only specify if * is provided. * Note that if an ArgParser/Permalink control is present, * and the querystring contains a zoom level, zoom will be set * by that, and this option will be ignored. * extent - {|Array} The initial extent of the map. * If provided as an array, the array should consist of * four values (left, bottom, right, top). * Only specify if
and are not provided. * * Examples: * (code) * // create a map with default options in an element with the id "map1" * var map = new OpenLayers.Map("map1"); * * // create a map with non-default options in an element with id "map2" * var options = { * projection: "EPSG:3857", * maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000), * center: new OpenLayers.LonLat(-12356463.476333, 5621521.4854095) * }; * var map = new OpenLayers.Map("map2", options); * * // map with non-default options - same as above but with a single argument, * // a restricted extent, and using arrays for bounds and center * var map = new OpenLayers.Map({ * div: "map_id", * projection: "EPSG:3857", * maxExtent: [-18924313.432222, -15538711.094146, 18924313.432222, 15538711.094146], * restrictedExtent: [-13358338.893333, -9608371.5085962, 13358338.893333, 9608371.5085962], * center: [-12356463.476333, 5621521.4854095] * }); * * // create a map without a reference to a container - call render later * var map = new OpenLayers.Map({ * projection: "EPSG:3857", * maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000) * }); * (end) */ initialize: function (div, options) { // If only one argument is provided, check if it is an object. if(arguments.length === 1 && typeof div === "object") { options = div; div = options && options.div; } // Simple-type defaults are set in class definition. // Now set complex-type defaults this.tileSize = new OpenLayers.Size(OpenLayers.Map.TILE_WIDTH, OpenLayers.Map.TILE_HEIGHT); this.paddingForPopups = new OpenLayers.Bounds(15, 15, 15, 15); this.theme = OpenLayers._getScriptLocation() + 'theme/default/style.css'; // backup original options this.options = OpenLayers.Util.extend({}, options); // now override default options OpenLayers.Util.extend(this, options); var projCode = this.projection instanceof OpenLayers.Projection ? this.projection.projCode : this.projection; OpenLayers.Util.applyDefaults(this, OpenLayers.Projection.defaults[projCode]); // allow extents and center to be arrays if (this.maxExtent && !(this.maxExtent instanceof OpenLayers.Bounds)) { this.maxExtent = new OpenLayers.Bounds(this.maxExtent); } if (this.minExtent && !(this.minExtent instanceof OpenLayers.Bounds)) { this.minExtent = new OpenLayers.Bounds(this.minExtent); } if (this.restrictedExtent && !(this.restrictedExtent instanceof OpenLayers.Bounds)) { this.restrictedExtent = new OpenLayers.Bounds(this.restrictedExtent); } if (this.center && !(this.center instanceof OpenLayers.LonLat)) { this.center = new OpenLayers.LonLat(this.center); } // initialize layers array this.layers = []; this.id = OpenLayers.Util.createUniqueID("OpenLayers.Map_"); this.div = OpenLayers.Util.getElement(div); if(!this.div) { this.div = document.createElement("div"); this.div.style.height = "1px"; this.div.style.width = "1px"; } OpenLayers.Element.addClass(this.div, 'olMap'); // the viewPortDiv is the outermost div we modify var id = this.id + "_OpenLayers_ViewPort"; this.viewPortDiv = OpenLayers.Util.createDiv(id, null, null, null, "relative", null, "hidden"); this.viewPortDiv.style.width = "100%"; this.viewPortDiv.style.height = "100%"; this.viewPortDiv.className = "olMapViewport"; this.div.appendChild(this.viewPortDiv); this.events = new OpenLayers.Events( this, this.viewPortDiv, null, this.fallThrough, {includeXY: true} ); if (OpenLayers.TileManager && this.tileManager !== null) { if (!(this.tileManager instanceof OpenLayers.TileManager)) { this.tileManager = new OpenLayers.TileManager(this.tileManager); } this.tileManager.addMap(this); } // the layerContainerDiv is the one that holds all the layers id = this.id + "_OpenLayers_Container"; this.layerContainerDiv = OpenLayers.Util.createDiv(id); this.layerContainerDiv.style.zIndex=this.Z_INDEX_BASE['Popup']-1; this.layerContainerOriginPx = {x: 0, y: 0}; this.applyTransform(); this.viewPortDiv.appendChild(this.layerContainerDiv); this.updateSize(); if(this.eventListeners instanceof Object) { this.events.on(this.eventListeners); } if (this.autoUpdateSize === true) { // updateSize on catching the window's resize // Note that this is ok, as updateSize() does nothing if the // map's size has not actually changed. this.updateSizeDestroy = OpenLayers.Function.bind(this.updateSize, this); OpenLayers.Event.observe(window, 'resize', this.updateSizeDestroy); } // only append link stylesheet if the theme property is set if(this.theme) { // check existing links for equivalent url var addNode = true; var nodes = document.getElementsByTagName('link'); for(var i=0, len=nodes.length; i=0; --i) { this.controls[i].destroy(); } this.controls = null; } if (this.layers != null) { for (var i = this.layers.length - 1; i>=0; --i) { //pass 'false' to destroy so that map wont try to set a new // baselayer after each baselayer is removed this.layers[i].destroy(false); } this.layers = null; } if (this.viewPortDiv && this.viewPortDiv.parentNode) { this.viewPortDiv.parentNode.removeChild(this.viewPortDiv); } this.viewPortDiv = null; if (this.tileManager) { this.tileManager.removeMap(this); this.tileManager = null; } if(this.eventListeners) { this.events.un(this.eventListeners); this.eventListeners = null; } this.events.destroy(); this.events = null; this.options = null; }, /** * APIMethod: setOptions * Change the map options * * Parameters: * options - {Object} Hashtable of options to tag to the map */ setOptions: function(options) { var updatePxExtent = this.minPx && options.restrictedExtent != this.restrictedExtent; OpenLayers.Util.extend(this, options); // force recalculation of minPx and maxPx updatePxExtent && this.moveTo(this.getCachedCenter(), this.zoom, { forceZoomChange: true }); }, /** * APIMethod: getTileSize * Get the tile size for the map * * Returns: * {} */ getTileSize: function() { return this.tileSize; }, /** * APIMethod: getBy * Get a list of objects given a property and a match item. * * Parameters: * array - {String} A property on the map whose value is an array. * property - {String} A property on each item of the given array. * match - {String | Object} A string to match. Can also be a regular * expression literal or object. In addition, it can be any object * with a method named test. For reqular expressions or other, if * match.test(map[array][i][property]) evaluates to true, the item will * be included in the array returned. If no items are found, an empty * array is returned. * * Returns: * {Array} An array of items where the given property matches the given * criteria. */ getBy: function(array, property, match) { var test = (typeof match.test == "function"); var found = OpenLayers.Array.filter(this[array], function(item) { return item[property] == match || (test && match.test(item[property])); }); return found; }, /** * APIMethod: getLayersBy * Get a list of layers with properties matching the given criteria. * * Parameters: * property - {String} A layer property to be matched. * match - {String | Object} A string to match. Can also be a regular * expression literal or object. In addition, it can be any object * with a method named test. For reqular expressions or other, if * match.test(layer[property]) evaluates to true, the layer will be * included in the array returned. If no layers are found, an empty * array is returned. * * Returns: * {Array()} A list of layers matching the given criteria. * An empty array is returned if no matches are found. */ getLayersBy: function(property, match) { return this.getBy("layers", property, match); }, /** * APIMethod: getLayersByName * Get a list of layers with names matching the given name. * * Parameters: * match - {String | Object} A layer name. The name can also be a regular * expression literal or object. In addition, it can be any object * with a method named test. For reqular expressions or other, if * name.test(layer.name) evaluates to true, the layer will be included * in the list of layers returned. If no layers are found, an empty * array is returned. * * Returns: * {Array()} A list of layers matching the given name. * An empty array is returned if no matches are found. */ getLayersByName: function(match) { return this.getLayersBy("name", match); }, /** * APIMethod: getLayersByClass * Get a list of layers of a given class (CLASS_NAME). * * Parameters: * match - {String | Object} A layer class name. The match can also be a * regular expression literal or object. In addition, it can be any * object with a method named test. For reqular expressions or other, * if type.test(layer.CLASS_NAME) evaluates to true, the layer will * be included in the list of layers returned. If no layers are * found, an empty array is returned. * * Returns: * {Array()} A list of layers matching the given class. * An empty array is returned if no matches are found. */ getLayersByClass: function(match) { return this.getLayersBy("CLASS_NAME", match); }, /** * APIMethod: getControlsBy * Get a list of controls with properties matching the given criteria. * * Parameters: * property - {String} A control property to be matched. * match - {String | Object} A string to match. Can also be a regular * expression literal or object. In addition, it can be any object * with a method named test. For reqular expressions or other, if * match.test(layer[property]) evaluates to true, the layer will be * included in the array returned. If no layers are found, an empty * array is returned. * * Returns: * {Array()} A list of controls matching the given * criteria. An empty array is returned if no matches are found. */ getControlsBy: function(property, match) { return this.getBy("controls", property, match); }, /** * APIMethod: getControlsByClass * Get a list of controls of a given class (CLASS_NAME). * * Parameters: * match - {String | Object} A control class name. The match can also be a * regular expression literal or object. In addition, it can be any * object with a method named test. For reqular expressions or other, * if type.test(control.CLASS_NAME) evaluates to true, the control will * be included in the list of controls returned. If no controls are * found, an empty array is returned. * * Returns: * {Array()} A list of controls matching the given class. * An empty array is returned if no matches are found. */ getControlsByClass: function(match) { return this.getControlsBy("CLASS_NAME", match); }, /********************************************************/ /* */ /* Layer Functions */ /* */ /* The following functions deal with adding and */ /* removing Layers to and from the Map */ /* */ /********************************************************/ /** * APIMethod: getLayer * Get a layer based on its id * * Parameters: * id - {String} A layer id * * Returns: * {} The Layer with the corresponding id from the map's * layer collection, or null if not found. */ getLayer: function(id) { var foundLayer = null; for (var i=0, len=this.layers.length; i} * zIdx - {int} */ setLayerZIndex: function (layer, zIdx) { layer.setZIndex( this.Z_INDEX_BASE[layer.isBaseLayer ? 'BaseLayer' : 'Overlay'] + zIdx * 5 ); }, /** * Method: resetLayersZIndex * Reset each layer's z-index based on layer's array index */ resetLayersZIndex: function() { for (var i=0, len=this.layers.length; i} * * Returns: * {Boolean} True if the layer has been added to the map. */ addLayer: function (layer) { for(var i = 0, len = this.layers.length; i < len; i++) { if (this.layers[i] == layer) { return false; } } if (this.events.triggerEvent("preaddlayer", {layer: layer}) === false) { return false; } if(this.allOverlays) { layer.isBaseLayer = false; } layer.div.className = "olLayerDiv"; layer.div.style.overflow = ""; this.setLayerZIndex(layer, this.layers.length); if (layer.isFixed) { this.viewPortDiv.appendChild(layer.div); } else { this.layerContainerDiv.appendChild(layer.div); } this.layers.push(layer); layer.setMap(this); if (layer.isBaseLayer || (this.allOverlays && !this.baseLayer)) { if (this.baseLayer == null) { // set the first baselaye we add as the baselayer this.setBaseLayer(layer); } else { layer.setVisibility(false); } } else { layer.redraw(); } this.events.triggerEvent("addlayer", {layer: layer}); layer.events.triggerEvent("added", {map: this, layer: layer}); layer.afterAdd(); return true; }, /** * APIMethod: addLayers * * Parameters: * layers - {Array()} */ addLayers: function (layers) { for (var i=0, len=layers.length; i} * setNewBaseLayer - {Boolean} Default is true */ removeLayer: function(layer, setNewBaseLayer) { if (this.events.triggerEvent("preremovelayer", {layer: layer}) === false) { return; } if (setNewBaseLayer == null) { setNewBaseLayer = true; } if (layer.isFixed) { this.viewPortDiv.removeChild(layer.div); } else { this.layerContainerDiv.removeChild(layer.div); } OpenLayers.Util.removeItem(this.layers, layer); layer.removeMap(this); layer.map = null; // if we removed the base layer, need to set a new one if(this.baseLayer == layer) { this.baseLayer = null; if(setNewBaseLayer) { for(var i=0, len=this.layers.length; i} * * Returns: * {Integer} The current (zero-based) index of the given layer in the map's * layer stack. Returns -1 if the layer isn't on the map. */ getLayerIndex: function (layer) { return OpenLayers.Util.indexOf(this.layers, layer); }, /** * APIMethod: setLayerIndex * Move the given layer to the specified (zero-based) index in the layer * list, changing its z-index in the map display. Use * map.getLayerIndex() to find out the current index of a layer. Note * that this cannot (or at least should not) be effectively used to * raise base layers above overlays. * * Parameters: * layer - {} * idx - {int} */ setLayerIndex: function (layer, idx) { var base = this.getLayerIndex(layer); if (idx < 0) { idx = 0; } else if (idx > this.layers.length) { idx = this.layers.length; } if (base != idx) { this.layers.splice(base, 1); this.layers.splice(idx, 0, layer); for (var i=0, len=this.layers.length; i} * delta - {int} */ raiseLayer: function (layer, delta) { var idx = this.getLayerIndex(layer) + delta; this.setLayerIndex(layer, idx); }, /** * APIMethod: setBaseLayer * Allows user to specify one of the currently-loaded layers as the Map's * new base layer. * * Parameters: * newBaseLayer - {} */ setBaseLayer: function(newBaseLayer) { if (newBaseLayer != this.baseLayer) { // ensure newBaseLayer is already loaded if (OpenLayers.Util.indexOf(this.layers, newBaseLayer) != -1) { // preserve center and scale when changing base layers var center = this.getCachedCenter(); var newResolution = OpenLayers.Util.getResolutionFromScale( this.getScale(), newBaseLayer.units ); // make the old base layer invisible if (this.baseLayer != null && !this.allOverlays) { this.baseLayer.setVisibility(false); } // set new baselayer this.baseLayer = newBaseLayer; if(!this.allOverlays || this.baseLayer.visibility) { this.baseLayer.setVisibility(true); // Layer may previously have been visible but not in range. // In this case we need to redraw it to make it visible. if (this.baseLayer.inRange === false) { this.baseLayer.redraw(); } } // recenter the map if (center != null) { // new zoom level derived from old scale var newZoom = this.getZoomForResolution( newResolution || this.resolution, true ); // zoom and force zoom change this.setCenter(center, newZoom, false, true); } this.events.triggerEvent("changebaselayer", { layer: this.baseLayer }); } } }, /********************************************************/ /* */ /* Control Functions */ /* */ /* The following functions deal with adding and */ /* removing Controls to and from the Map */ /* */ /********************************************************/ /** * APIMethod: addControl * Add the passed over control to the map. Optionally * position the control at the given pixel. * * Parameters: * control - {} * px - {} */ addControl: function (control, px) { this.controls.push(control); this.addControlToMap(control, px); }, /** * APIMethod: addControls * Add all of the passed over controls to the map. * You can pass over an optional second array * with pixel-objects to position the controls. * The indices of the two arrays should match and * you can add null as pixel for those controls * you want to be autopositioned. * * Parameters: * controls - {Array()} * pixels - {Array()} */ addControls: function (controls, pixels) { var pxs = (arguments.length === 1) ? [] : pixels; for (var i=0, len=controls.length; i} * px - {} */ addControlToMap: function (control, px) { // If a control doesn't have a div at this point, it belongs in the // viewport. control.outsideViewport = (control.div != null); // If the map has a displayProjection, and the control doesn't, set // the display projection. if (this.displayProjection && !control.displayProjection) { control.displayProjection = this.displayProjection; } control.setMap(this); var div = control.draw(px); if (div) { if(!control.outsideViewport) { div.style.zIndex = this.Z_INDEX_BASE['Control'] + this.controls.length; this.viewPortDiv.appendChild( div ); } } if(control.autoActivate) { control.activate(); } }, /** * APIMethod: getControl * * Parameters: * id - {String} ID of the control to return. * * Returns: * {} The control from the map's list of controls * which has a matching 'id'. If none found, * returns null. */ getControl: function (id) { var returnControl = null; for(var i=0, len=this.controls.length; i} The control to remove. */ removeControl: function (control) { //make sure control is non-null and actually part of our map if ( (control) && (control == this.getControl(control.id)) ) { if (control.div && (control.div.parentNode == this.viewPortDiv)) { this.viewPortDiv.removeChild(control.div); } OpenLayers.Util.removeItem(this.controls, control); } }, /********************************************************/ /* */ /* Popup Functions */ /* */ /* The following functions deal with adding and */ /* removing Popups to and from the Map */ /* */ /********************************************************/ /** * APIMethod: addPopup * * Parameters: * popup - {} * exclusive - {Boolean} If true, closes all other popups first */ addPopup: function(popup, exclusive) { if (exclusive) { //remove all other popups from screen for (var i = this.popups.length - 1; i >= 0; --i) { this.removePopup(this.popups[i]); } } popup.map = this; this.popups.push(popup); var popupDiv = popup.draw(); if (popupDiv) { popupDiv.style.zIndex = this.Z_INDEX_BASE['Popup'] + this.popups.length; this.layerContainerDiv.appendChild(popupDiv); } }, /** * APIMethod: removePopup * * Parameters: * popup - {} */ removePopup: function(popup) { OpenLayers.Util.removeItem(this.popups, popup); if (popup.div) { try { this.layerContainerDiv.removeChild(popup.div); } catch (e) { } // Popups sometimes apparently get disconnected // from the layerContainerDiv, and cause complaints. } popup.map = null; }, /********************************************************/ /* */ /* Container Div Functions */ /* */ /* The following functions deal with the access to */ /* and maintenance of the size of the container div */ /* */ /********************************************************/ /** * APIMethod: getSize * * Returns: * {} An object that represents the * size, in pixels, of the div into which OpenLayers * has been loaded. * Note - A clone() of this locally cached variable is * returned, so as not to allow users to modify it. */ getSize: function () { var size = null; if (this.size != null) { size = this.size.clone(); } return size; }, /** * APIMethod: updateSize * This function should be called by any external code which dynamically * changes the size of the map div (because mozilla wont let us catch * the "onresize" for an element) */ updateSize: function() { // the div might have moved on the page, also var newSize = this.getCurrentSize(); if (newSize && !isNaN(newSize.h) && !isNaN(newSize.w)) { this.events.clearMouseCache(); var oldSize = this.getSize(); if (oldSize == null) { this.size = oldSize = newSize; } if (!newSize.equals(oldSize)) { // store the new size this.size = newSize; //notify layers of mapresize for(var i=0, len=this.layers.length; i} A new object with the dimensions * of the map div */ getCurrentSize: function() { var size = new OpenLayers.Size(this.div.clientWidth, this.div.clientHeight); if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) { size.w = this.div.offsetWidth; size.h = this.div.offsetHeight; } if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) { size.w = parseInt(this.div.style.width); size.h = parseInt(this.div.style.height); } return size; }, /** * Method: calculateBounds * * Parameters: * center - {} Default is this.getCenter() * resolution - {float} Default is this.getResolution() * * Returns: * {} A bounds based on resolution, center, and * current mapsize. */ calculateBounds: function(center, resolution) { var extent = null; if (center == null) { center = this.getCachedCenter(); } if (resolution == null) { resolution = this.getResolution(); } if ((center != null) && (resolution != null)) { var halfWDeg = (this.size.w * resolution) / 2; var halfHDeg = (this.size.h * resolution) / 2; extent = new OpenLayers.Bounds(center.lon - halfWDeg, center.lat - halfHDeg, center.lon + halfWDeg, center.lat + halfHDeg); } return extent; }, /********************************************************/ /* */ /* Zoom, Center, Pan Functions */ /* */ /* The following functions handle the validation, */ /* getting and setting of the Zoom Level and Center */ /* as well as the panning of the Map */ /* */ /********************************************************/ /** * APIMethod: getCenter * * Returns: * {} */ getCenter: function () { var center = null; var cachedCenter = this.getCachedCenter(); if (cachedCenter) { center = cachedCenter.clone(); } return center; }, /** * Method: getCachedCenter * * Returns: * {} */ getCachedCenter: function() { if (!this.center && this.size) { this.center = this.getLonLatFromViewPortPx({ x: this.size.w / 2, y: this.size.h / 2 }); } return this.center; }, /** * APIMethod: getZoom * * Returns: * {Integer} */ getZoom: function () { return this.zoom; }, /** * APIMethod: pan * Allows user to pan by a value of screen pixels * * Parameters: * dx - {Integer} * dy - {Integer} * options - {Object} Options to configure panning: * - *animate* {Boolean} Use panTo instead of setCenter. Default is true. * - *dragging* {Boolean} Call setCenter with dragging true. Default is * false. */ pan: function(dx, dy, options) { options = OpenLayers.Util.applyDefaults(options, { animate: true, dragging: false }); if (options.dragging) { if (dx != 0 || dy != 0) { this.moveByPx(dx, dy); } } else { // getCenter var centerPx = this.getViewPortPxFromLonLat(this.getCachedCenter()); // adjust var newCenterPx = centerPx.add(dx, dy); if (this.dragging || !newCenterPx.equals(centerPx)) { var newCenterLonLat = this.getLonLatFromViewPortPx(newCenterPx); if (options.animate) { this.panTo(newCenterLonLat); } else { this.moveTo(newCenterLonLat); if(this.dragging) { this.dragging = false; this.events.triggerEvent("moveend"); } } } } }, /** * APIMethod: panTo * Allows user to pan to a new lonlat * If the new lonlat is in the current extent the map will slide smoothly * * Parameters: * lonlat - {} */ panTo: function(lonlat) { if (this.panTween && this.getExtent().scale(this.panRatio).containsLonLat(lonlat)) { var center = this.getCachedCenter(); // center will not change, don't do nothing if (lonlat.equals(center)) { return; } var from = this.getPixelFromLonLat(center); var to = this.getPixelFromLonLat(lonlat); var vector = { x: to.x - from.x, y: to.y - from.y }; var last = { x: 0, y: 0 }; this.panTween.start( { x: 0, y: 0 }, vector, this.panDuration, { callbacks: { eachStep: OpenLayers.Function.bind(function(px) { var x = px.x - last.x, y = px.y - last.y; this.moveByPx(x, y); last.x = Math.round(px.x); last.y = Math.round(px.y); }, this), done: OpenLayers.Function.bind(function(px) { this.moveTo(lonlat); this.dragging = false; this.events.triggerEvent("moveend"); }, this) } }); } else { this.setCenter(lonlat); } }, /** * APIMethod: setCenter * Set the map center (and optionally, the zoom level). * * Parameters: * lonlat - {|Array} The new center location. * If provided as array, the first value is the x coordinate, * and the 2nd value is the y coordinate. * zoom - {Integer} Optional zoom level. * dragging - {Boolean} Specifies whether or not to trigger * movestart/end events * forceZoomChange - {Boolean} Specifies whether or not to trigger zoom * change events (needed on baseLayer change) * * TBD: reconsider forceZoomChange in 3.0 */ setCenter: function(lonlat, zoom, dragging, forceZoomChange) { if (this.panTween) { this.panTween.stop(); } if (this.zoomTween) { this.zoomTween.stop(); } this.moveTo(lonlat, zoom, { 'dragging': dragging, 'forceZoomChange': forceZoomChange }); }, /** * Method: moveByPx * Drag the map by pixels. * * Parameters: * dx - {Number} * dy - {Number} */ moveByPx: function(dx, dy) { var hw = this.size.w / 2; var hh = this.size.h / 2; var x = hw + dx; var y = hh + dy; var wrapDateLine = this.baseLayer.wrapDateLine; var xRestriction = 0; var yRestriction = 0; if (this.restrictedExtent) { xRestriction = hw; yRestriction = hh; // wrapping the date line makes no sense for restricted extents wrapDateLine = false; } dx = wrapDateLine || x <= this.maxPx.x - xRestriction && x >= this.minPx.x + xRestriction ? Math.round(dx) : 0; dy = y <= this.maxPx.y - yRestriction && y >= this.minPx.y + yRestriction ? Math.round(dy) : 0; if (dx || dy) { if (!this.dragging) { this.dragging = true; this.events.triggerEvent("movestart"); } this.center = null; if (dx) { this.layerContainerOriginPx.x -= dx; this.minPx.x -= dx; this.maxPx.x -= dx; } if (dy) { this.layerContainerOriginPx.y -= dy; this.minPx.y -= dy; this.maxPx.y -= dy; } this.applyTransform(); var layer, i, len; for (i=0, len=this.layers.length; i's maxExtent. */ adjustZoom: function(zoom) { if (this.baseLayer && this.baseLayer.wrapDateLine) { var resolution, resolutions = this.baseLayer.resolutions, maxResolution = this.getMaxExtent().getWidth() / this.size.w; if (this.getResolutionForZoom(zoom) > maxResolution) { if (this.fractionalZoom) { zoom = this.getZoomForResolution(maxResolution); } else { for (var i=zoom|0, ii=resolutions.length; i set to true, this will be the * first zoom level that shows no more than one world width in the current * map viewport. Components that rely on this value (e.g. zoom sliders) * should also listen to the map's "updatesize" event and call this method * in the "updatesize" listener. * * Returns: * {Number} Minimum zoom level that shows a map not wider than its * 's maxExtent. This is an Integer value, unless the map is * configured with set to true. */ getMinZoom: function() { return this.adjustZoom(0); }, /** * Method: moveTo * * Parameters: * lonlat - {} * zoom - {Integer} * options - {Object} */ moveTo: function(lonlat, zoom, options) { if (lonlat != null && !(lonlat instanceof OpenLayers.LonLat)) { lonlat = new OpenLayers.LonLat(lonlat); } if (!options) { options = {}; } if (zoom != null) { zoom = parseFloat(zoom); if (!this.fractionalZoom) { zoom = Math.round(zoom); } } var requestedZoom = zoom; zoom = this.adjustZoom(zoom); if (zoom !== requestedZoom) { // zoom was adjusted, so keep old lonlat to avoid panning lonlat = this.getCenter(); } // dragging is false by default var dragging = options.dragging || this.dragging; // forceZoomChange is false by default var forceZoomChange = options.forceZoomChange; if (!this.getCachedCenter() && !this.isValidLonLat(lonlat)) { lonlat = this.maxExtent.getCenterLonLat(); this.center = lonlat.clone(); } if(this.restrictedExtent != null) { // In 3.0, decide if we want to change interpretation of maxExtent. if(lonlat == null) { lonlat = this.center; } if(zoom == null) { zoom = this.getZoom(); } var resolution = this.getResolutionForZoom(zoom); var extent = this.calculateBounds(lonlat, resolution); if(!this.restrictedExtent.containsBounds(extent)) { var maxCenter = this.restrictedExtent.getCenterLonLat(); if(extent.getWidth() > this.restrictedExtent.getWidth()) { lonlat = new OpenLayers.LonLat(maxCenter.lon, lonlat.lat); } else if(extent.left < this.restrictedExtent.left) { lonlat = lonlat.add(this.restrictedExtent.left - extent.left, 0); } else if(extent.right > this.restrictedExtent.right) { lonlat = lonlat.add(this.restrictedExtent.right - extent.right, 0); } if(extent.getHeight() > this.restrictedExtent.getHeight()) { lonlat = new OpenLayers.LonLat(lonlat.lon, maxCenter.lat); } else if(extent.bottom < this.restrictedExtent.bottom) { lonlat = lonlat.add(0, this.restrictedExtent.bottom - extent.bottom); } else if(extent.top > this.restrictedExtent.top) { lonlat = lonlat.add(0, this.restrictedExtent.top - extent.top); } } } var zoomChanged = forceZoomChange || ( (this.isValidZoomLevel(zoom)) && (zoom != this.getZoom()) ); var centerChanged = (this.isValidLonLat(lonlat)) && (!lonlat.equals(this.center)); // if neither center nor zoom will change, no need to do anything if (zoomChanged || centerChanged || dragging) { dragging || this.events.triggerEvent("movestart", { zoomChanged: zoomChanged }); if (centerChanged) { if (!zoomChanged && this.center) { // if zoom hasnt changed, just slide layerContainer // (must be done before setting this.center to new value) this.centerLayerContainer(lonlat); } this.center = lonlat.clone(); } var res = zoomChanged ? this.getResolutionForZoom(zoom) : this.getResolution(); // (re)set the layerContainerDiv's location if (zoomChanged || this.layerContainerOrigin == null) { this.layerContainerOrigin = this.getCachedCenter(); this.layerContainerOriginPx.x = 0; this.layerContainerOriginPx.y = 0; this.applyTransform(); var maxExtent = this.getMaxExtent({restricted: true}); var maxExtentCenter = maxExtent.getCenterLonLat(); var lonDelta = this.center.lon - maxExtentCenter.lon; var latDelta = maxExtentCenter.lat - this.center.lat; var extentWidth = Math.round(maxExtent.getWidth() / res); var extentHeight = Math.round(maxExtent.getHeight() / res); this.minPx = { x: (this.size.w - extentWidth) / 2 - lonDelta / res, y: (this.size.h - extentHeight) / 2 - latDelta / res }; this.maxPx = { x: this.minPx.x + Math.round(maxExtent.getWidth() / res), y: this.minPx.y + Math.round(maxExtent.getHeight() / res) }; } if (zoomChanged) { this.zoom = zoom; this.resolution = res; } var bounds = this.getExtent(); //send the move call to the baselayer and all the overlays if(this.baseLayer.visibility) { this.baseLayer.moveTo(bounds, zoomChanged, options.dragging); options.dragging || this.baseLayer.events.triggerEvent( "moveend", {zoomChanged: zoomChanged} ); } bounds = this.baseLayer.getExtent(); for (var i=this.layers.length-1; i>=0; --i) { var layer = this.layers[i]; if (layer !== this.baseLayer && !layer.isBaseLayer) { var inRange = layer.calculateInRange(); if (layer.inRange != inRange) { // the inRange property has changed. If the layer is // no longer in range, we turn it off right away. If // the layer is no longer out of range, the moveTo // call below will turn on the layer. layer.inRange = inRange; if (!inRange) { layer.display(false); } this.events.triggerEvent("changelayer", { layer: layer, property: "visibility" }); } if (inRange && layer.visibility) { layer.moveTo(bounds, zoomChanged, options.dragging); options.dragging || layer.events.triggerEvent( "moveend", {zoomChanged: zoomChanged} ); } } } this.events.triggerEvent("move"); dragging || this.events.triggerEvent("moveend"); if (zoomChanged) { //redraw popups for (var i=0, len=this.popups.length; i} */ centerLayerContainer: function (lonlat) { var originPx = this.getViewPortPxFromLonLat(this.layerContainerOrigin); var newPx = this.getViewPortPxFromLonLat(lonlat); if ((originPx != null) && (newPx != null)) { var oldLeft = this.layerContainerOriginPx.x; var oldTop = this.layerContainerOriginPx.y; var newLeft = Math.round(originPx.x - newPx.x); var newTop = Math.round(originPx.y - newPx.y); this.applyTransform( (this.layerContainerOriginPx.x = newLeft), (this.layerContainerOriginPx.y = newTop)); var dx = oldLeft - newLeft; var dy = oldTop - newTop; this.minPx.x -= dx; this.maxPx.x -= dx; this.minPx.y -= dy; this.maxPx.y -= dy; } }, /** * Method: isValidZoomLevel * * Parameters: * zoomLevel - {Integer} * * Returns: * {Boolean} Whether or not the zoom level passed in is non-null and * within the min/max range of zoom levels. */ isValidZoomLevel: function(zoomLevel) { return ( (zoomLevel != null) && (zoomLevel >= 0) && (zoomLevel < this.getNumZoomLevels()) ); }, /** * Method: isValidLonLat * * Parameters: * lonlat - {} * * Returns: * {Boolean} Whether or not the lonlat passed in is non-null and within * the maxExtent bounds */ isValidLonLat: function(lonlat) { var valid = false; if (lonlat != null) { var maxExtent = this.getMaxExtent(); var worldBounds = this.baseLayer.wrapDateLine && maxExtent; valid = maxExtent.containsLonLat(lonlat, {worldBounds: worldBounds}); } return valid; }, /********************************************************/ /* */ /* Layer Options */ /* */ /* Accessor functions to Layer Options parameters */ /* */ /********************************************************/ /** * APIMethod: getProjection * This method returns a string representing the projection. In * the case of projection support, this will be the srsCode which * is loaded -- otherwise it will simply be the string value that * was passed to the projection at startup. * * FIXME: In 3.0, we will remove getProjectionObject, and instead * return a Projection object from this function. * * Returns: * {String} The Projection string from the base layer or null. */ getProjection: function() { var projection = this.getProjectionObject(); return projection ? projection.getCode() : null; }, /** * APIMethod: getProjectionObject * Returns the projection obect from the baselayer. * * Returns: * {} The Projection of the base layer. */ getProjectionObject: function() { var projection = null; if (this.baseLayer != null) { projection = this.baseLayer.projection; } return projection; }, /** * APIMethod: getMaxResolution * * Returns: * {String} The Map's Maximum Resolution */ getMaxResolution: function() { var maxResolution = null; if (this.baseLayer != null) { maxResolution = this.baseLayer.maxResolution; } return maxResolution; }, /** * APIMethod: getMaxExtent * * Parameters: * options - {Object} * * Allowed Options: * restricted - {Boolean} If true, returns restricted extent (if it is * available.) * * Returns: * {} The maxExtent property as set on the current * baselayer, unless the 'restricted' option is set, in which case * the 'restrictedExtent' option from the map is returned (if it * is set). */ getMaxExtent: function (options) { var maxExtent = null; if(options && options.restricted && this.restrictedExtent){ maxExtent = this.restrictedExtent; } else if (this.baseLayer != null) { maxExtent = this.baseLayer.maxExtent; } return maxExtent; }, /** * APIMethod: getNumZoomLevels * * Returns: * {Integer} The total number of zoom levels that can be displayed by the * current baseLayer. */ getNumZoomLevels: function() { var numZoomLevels = null; if (this.baseLayer != null) { numZoomLevels = this.baseLayer.numZoomLevels; } return numZoomLevels; }, /********************************************************/ /* */ /* Baselayer Functions */ /* */ /* The following functions, all publicly exposed */ /* in the API?, are all merely wrappers to the */ /* the same calls on whatever layer is set as */ /* the current base layer */ /* */ /********************************************************/ /** * APIMethod: getExtent * * Returns: * {} A Bounds object which represents the lon/lat * bounds of the current viewPort. * If no baselayer is set, returns null. */ getExtent: function () { var extent = null; if (this.baseLayer != null) { extent = this.baseLayer.getExtent(); } return extent; }, /** * APIMethod: getResolution * * Returns: * {Float} The current resolution of the map. * If no baselayer is set, returns null. */ getResolution: function () { var resolution = null; if (this.baseLayer != null) { resolution = this.baseLayer.getResolution(); } else if(this.allOverlays === true && this.layers.length > 0) { // while adding the 1st layer to the map in allOverlays mode, // this.baseLayer is not set yet when we need the resolution // for calculateInRange. resolution = this.layers[0].getResolution(); } return resolution; }, /** * APIMethod: getUnits * * Returns: * {Float} The current units of the map. * If no baselayer is set, returns null. */ getUnits: function () { var units = null; if (this.baseLayer != null) { units = this.baseLayer.units; } return units; }, /** * APIMethod: getScale * * Returns: * {Float} The current scale denominator of the map. * If no baselayer is set, returns null. */ getScale: function () { var scale = null; if (this.baseLayer != null) { var res = this.getResolution(); var units = this.baseLayer.units; scale = OpenLayers.Util.getScaleFromResolution(res, units); } return scale; }, /** * APIMethod: getZoomForExtent * * Parameters: * bounds - {} * closest - {Boolean} Find the zoom level that most closely fits the * specified bounds. Note that this may result in a zoom that does * not exactly contain the entire extent. * Default is false. * * Returns: * {Integer} A suitable zoom level for the specified bounds. * If no baselayer is set, returns null. */ getZoomForExtent: function (bounds, closest) { var zoom = null; if (this.baseLayer != null) { zoom = this.baseLayer.getZoomForExtent(bounds, closest); } return zoom; }, /** * APIMethod: getResolutionForZoom * * Parameters: * zoom - {Float} * * Returns: * {Float} A suitable resolution for the specified zoom. If no baselayer * is set, returns null. */ getResolutionForZoom: function(zoom) { var resolution = null; if(this.baseLayer) { resolution = this.baseLayer.getResolutionForZoom(zoom); } return resolution; }, /** * APIMethod: getZoomForResolution * * Parameters: * resolution - {Float} * closest - {Boolean} Find the zoom level that corresponds to the absolute * closest resolution, which may result in a zoom whose corresponding * resolution is actually smaller than we would have desired (if this * is being called from a getZoomForExtent() call, then this means that * the returned zoom index might not actually contain the entire * extent specified... but it'll be close). * Default is false. * * Returns: * {Integer} A suitable zoom level for the specified resolution. * If no baselayer is set, returns null. */ getZoomForResolution: function(resolution, closest) { var zoom = null; if (this.baseLayer != null) { zoom = this.baseLayer.getZoomForResolution(resolution, closest); } return zoom; }, /********************************************************/ /* */ /* Zooming Functions */ /* */ /* The following functions, all publicly exposed */ /* in the API, are all merely wrappers to the */ /* the setCenter() function */ /* */ /********************************************************/ /** * APIMethod: zoomTo * Zoom to a specific zoom level. Zooming will be animated unless the map * is configured with {zoomMethod: null}. To zoom without animation, use * without a lonlat argument. * * Parameters: * zoom - {Integer} */ zoomTo: function(zoom, xy) { // non-API arguments: // xy - {} optional zoom origin var map = this; if (map.isValidZoomLevel(zoom)) { if (map.baseLayer.wrapDateLine) { zoom = map.adjustZoom(zoom); } if (map.zoomTween) { var currentRes = map.getResolution(), targetRes = map.getResolutionForZoom(zoom), start = {scale: 1}, end = {scale: currentRes / targetRes}; if (map.zoomTween.playing && map.zoomTween.duration < 3 * map.zoomDuration) { // update the end scale, and reuse the running zoomTween map.zoomTween.finish = { scale: map.zoomTween.finish.scale * end.scale }; } else { if (!xy) { var size = map.getSize(); xy = {x: size.w / 2, y: size.h / 2}; } map.zoomTween.start(start, end, map.zoomDuration, { minFrameRate: 50, // don't spend much time zooming callbacks: { eachStep: function(data) { var containerOrigin = map.layerContainerOriginPx, scale = data.scale, dx = ((scale - 1) * (containerOrigin.x - xy.x)) | 0, dy = ((scale - 1) * (containerOrigin.y - xy.y)) | 0; map.applyTransform(containerOrigin.x + dx, containerOrigin.y + dy, scale); }, done: function(data) { map.applyTransform(); var resolution = map.getResolution() / data.scale, zoom = map.getZoomForResolution(resolution, true) map.moveTo(map.getZoomTargetCenter(xy, resolution), zoom, true); } } }); } } else { var center = xy ? map.getZoomTargetCenter(xy, map.getResolutionForZoom(zoom)) : null; map.setCenter(center, zoom); } } }, /** * APIMethod: zoomIn * */ zoomIn: function() { this.zoomTo(this.getZoom() + 1); }, /** * APIMethod: zoomOut * */ zoomOut: function() { this.zoomTo(this.getZoom() - 1); }, /** * APIMethod: zoomToExtent * Zoom to the passed in bounds, recenter * * Parameters: * bounds - {|Array} If provided as an array, the array * should consist of four values (left, bottom, right, top). * closest - {Boolean} Find the zoom level that most closely fits the * specified bounds. Note that this may result in a zoom that does * not exactly contain the entire extent. * Default is false. * */ zoomToExtent: function(bounds, closest) { if (!(bounds instanceof OpenLayers.Bounds)) { bounds = new OpenLayers.Bounds(bounds); } var center = bounds.getCenterLonLat(); if (this.baseLayer.wrapDateLine) { var maxExtent = this.getMaxExtent(); //fix straddling bounds (in the case of a bbox that straddles the // dateline, it's left and right boundaries will appear backwards. // we fix this by allowing a right value that is greater than the // max value at the dateline -- this allows us to pass a valid // bounds to calculate zoom) // bounds = bounds.clone(); while (bounds.right < bounds.left) { bounds.right += maxExtent.getWidth(); } //if the bounds was straddling (see above), then the center point // we got from it was wrong. So we take our new bounds and ask it // for the center. // center = bounds.getCenterLonLat().wrapDateLine(maxExtent); } this.setCenter(center, this.getZoomForExtent(bounds, closest)); }, /** * APIMethod: zoomToMaxExtent * Zoom to the full extent and recenter. * * Parameters: * options - {Object} * * Allowed Options: * restricted - {Boolean} True to zoom to restricted extent if it is * set. Defaults to true. */ zoomToMaxExtent: function(options) { //restricted is true by default var restricted = (options) ? options.restricted : true; var maxExtent = this.getMaxExtent({ 'restricted': restricted }); this.zoomToExtent(maxExtent); }, /** * APIMethod: zoomToScale * Zoom to a specified scale * * Parameters: * scale - {float} * closest - {Boolean} Find the zoom level that most closely fits the * specified scale. Note that this may result in a zoom that does * not exactly contain the entire extent. * Default is false. * */ zoomToScale: function(scale, closest) { var res = OpenLayers.Util.getResolutionFromScale(scale, this.baseLayer.units); var halfWDeg = (this.size.w * res) / 2; var halfHDeg = (this.size.h * res) / 2; var center = this.getCachedCenter(); var extent = new OpenLayers.Bounds(center.lon - halfWDeg, center.lat - halfHDeg, center.lon + halfWDeg, center.lat + halfHDeg); this.zoomToExtent(extent, closest); }, /********************************************************/ /* */ /* Translation Functions */ /* */ /* The following functions translate between */ /* LonLat, LayerPx, and ViewPortPx */ /* */ /********************************************************/ // // TRANSLATION: LonLat <-> ViewPortPx // /** * Method: getLonLatFromViewPortPx * * Parameters: * viewPortPx - {|Object} An OpenLayers.Pixel or * an object with a 'x' * and 'y' properties. * * Returns: * {} An OpenLayers.LonLat which is the passed-in view * port , translated into lon/lat * by the current base layer. */ getLonLatFromViewPortPx: function (viewPortPx) { var lonlat = null; if (this.baseLayer != null) { lonlat = this.baseLayer.getLonLatFromViewPortPx(viewPortPx); } return lonlat; }, /** * APIMethod: getViewPortPxFromLonLat * * Parameters: * lonlat - {} * * Returns: * {} An OpenLayers.Pixel which is the passed-in * , translated into view port * pixels by the current base layer. */ getViewPortPxFromLonLat: function (lonlat) { var px = null; if (this.baseLayer != null) { px = this.baseLayer.getViewPortPxFromLonLat(lonlat); } return px; }, /** * Method: getZoomTargetCenter * * Parameters: * xy - {} The zoom origin pixel location on the screen * resolution - {Float} The resolution we want to get the center for * * Returns: * {} The location of the map center after the * transformation described by the origin xy and the target resolution. */ getZoomTargetCenter: function (xy, resolution) { var lonlat = null, size = this.getSize(), deltaX = size.w/2 - xy.x, deltaY = xy.y - size.h/2, zoomPoint = this.getLonLatFromPixel(xy); if (zoomPoint) { lonlat = new OpenLayers.LonLat( zoomPoint.lon + deltaX * resolution, zoomPoint.lat + deltaY * resolution ); } return lonlat; }, // // CONVENIENCE TRANSLATION FUNCTIONS FOR API // /** * APIMethod: getLonLatFromPixel * * Parameters: * px - {|Object} An OpenLayers.Pixel or an object with * a 'x' and 'y' properties. * * Returns: * {} An OpenLayers.LonLat corresponding to the given * OpenLayers.Pixel, translated into lon/lat by the * current base layer */ getLonLatFromPixel: function (px) { return this.getLonLatFromViewPortPx(px); }, /** * APIMethod: getPixelFromLonLat * Returns a pixel location given a map location. The map location is * translated to an integer pixel location (in viewport pixel * coordinates) by the current base layer. * * Parameters: * lonlat - {} A map location. * * Returns: * {} An OpenLayers.Pixel corresponding to the * translated into view port pixels by the current * base layer. */ getPixelFromLonLat: function (lonlat) { var px = this.getViewPortPxFromLonLat(lonlat); px.x = Math.round(px.x); px.y = Math.round(px.y); return px; }, /** * Method: getGeodesicPixelSize * * Parameters: * px - {} The pixel to get the geodesic length for. If * not provided, the center pixel of the map viewport will be used. * * Returns: * {} The geodesic size of the pixel in kilometers. */ getGeodesicPixelSize: function(px) { var lonlat = px ? this.getLonLatFromPixel(px) : ( this.getCachedCenter() || new OpenLayers.LonLat(0, 0)); var res = this.getResolution(); var left = lonlat.add(-res / 2, 0); var right = lonlat.add(res / 2, 0); var bottom = lonlat.add(0, -res / 2); var top = lonlat.add(0, res / 2); var dest = new OpenLayers.Projection("EPSG:4326"); var source = this.getProjectionObject() || dest; if(!source.equals(dest)) { left.transform(source, dest); right.transform(source, dest); bottom.transform(source, dest); top.transform(source, dest); } return new OpenLayers.Size( OpenLayers.Util.distVincenty(left, right), OpenLayers.Util.distVincenty(bottom, top) ); }, // // TRANSLATION: ViewPortPx <-> LayerPx // /** * APIMethod: getViewPortPxFromLayerPx * * Parameters: * layerPx - {} * * Returns: * {} Layer Pixel translated into ViewPort Pixel * coordinates */ getViewPortPxFromLayerPx:function(layerPx) { var viewPortPx = null; if (layerPx != null) { var dX = this.layerContainerOriginPx.x; var dY = this.layerContainerOriginPx.y; viewPortPx = layerPx.add(dX, dY); } return viewPortPx; }, /** * APIMethod: getLayerPxFromViewPortPx * * Parameters: * viewPortPx - {} * * Returns: * {} ViewPort Pixel translated into Layer Pixel * coordinates */ getLayerPxFromViewPortPx:function(viewPortPx) { var layerPx = null; if (viewPortPx != null) { var dX = -this.layerContainerOriginPx.x; var dY = -this.layerContainerOriginPx.y; layerPx = viewPortPx.add(dX, dY); if (isNaN(layerPx.x) || isNaN(layerPx.y)) { layerPx = null; } } return layerPx; }, // // TRANSLATION: LonLat <-> LayerPx // /** * Method: getLonLatFromLayerPx * * Parameters: * px - {} * * Returns: * {} */ getLonLatFromLayerPx: function (px) { //adjust for displacement of layerContainerDiv px = this.getViewPortPxFromLayerPx(px); return this.getLonLatFromViewPortPx(px); }, /** * APIMethod: getLayerPxFromLonLat * * Parameters: * lonlat - {} lonlat * * Returns: * {} An OpenLayers.Pixel which is the passed-in * , translated into layer pixels * by the current base layer */ getLayerPxFromLonLat: function (lonlat) { //adjust for displacement of layerContainerDiv var px = this.getPixelFromLonLat(lonlat); return this.getLayerPxFromViewPortPx(px); }, /** * Method: applyTransform * Applies the given transform to the . This method has * a 2-stage fallback from translate3d/scale3d via translate/scale to plain * style.left/style.top, in which case no scaling is supported. * * Parameters: * x - {Number} x parameter for the translation. Defaults to the x value of * the map's * y - {Number} y parameter for the translation. Defaults to the y value of * the map's * scale - {Number} scale. Defaults to 1 if not provided. */ applyTransform: function(x, y, scale) { scale = scale || 1; var origin = this.layerContainerOriginPx, needTransform = scale !== 1; x = x || origin.x; y = y || origin.y; var style = this.layerContainerDiv.style, transform = this.applyTransform.transform, template = this.applyTransform.template; if (transform === undefined) { transform = OpenLayers.Util.vendorPrefix.style('transform'); this.applyTransform.transform = transform; if (transform) { // Try translate3d, but only if the viewPortDiv has a transform // defined in a stylesheet var computedStyle = OpenLayers.Element.getStyle(this.viewPortDiv, OpenLayers.Util.vendorPrefix.css('transform')); if (!computedStyle || computedStyle !== 'none') { template = ['translate3d(', ',0) ', 'scale3d(', ',1)']; style[transform] = [template[0], '0,0', template[1]].join(''); } // If no transform is defined in the stylesheet or translate3d // does not stick, use translate and scale if (!template || !~style[transform].indexOf(template[0])) { template = ['translate(', ') ', 'scale(', ')']; } this.applyTransform.template = template; } } // If we do 3d transforms, we always want to use them. If we do 2d // transforms, we only use them when we need to. if (transform !== null && (template[0] === 'translate3d(' || needTransform === true)) { // Our 2d transforms are combined with style.left and style.top, so // adjust x and y values and set the origin as left and top if (needTransform === true && template[0] === 'translate(') { x -= origin.x; y -= origin.y; style.left = origin.x + 'px'; style.top = origin.y + 'px'; } style[transform] = [ template[0], x, 'px,', y, 'px', template[1], template[2], scale, ',', scale, template[3] ].join(''); } else { style.left = x + 'px'; style.top = y + 'px'; // We previously might have had needTransform, so remove transform if (transform !== null) { style[transform] = ''; } } }, CLASS_NAME: "OpenLayers.Map" }); /** * Constant: TILE_WIDTH * {Integer} 256 Default tile width (unless otherwise specified) */ OpenLayers.Map.TILE_WIDTH = 256; /** * Constant: TILE_HEIGHT * {Integer} 256 Default tile height (unless otherwise specified) */ OpenLayers.Map.TILE_HEIGHT = 256; /* ====================================================================== OpenLayers/Layer.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/BaseTypes/Class.js * @requires OpenLayers/Map.js * @requires OpenLayers/Projection.js */ /** * Class: OpenLayers.Layer */ OpenLayers.Layer = OpenLayers.Class({ /** * APIProperty: id * {String} */ id: null, /** * APIProperty: name * {String} */ name: null, /** * APIProperty: div * {DOMElement} */ div: null, /** * APIProperty: opacity * {Float} The layer's opacity. Float number between 0.0 and 1.0. Default * is 1. */ opacity: 1, /** * APIProperty: alwaysInRange * {Boolean} If a layer's display should not be scale-based, this should * be set to true. This will cause the layer, as an overlay, to always * be 'active', by always returning true from the calculateInRange() * function. * * If not explicitly specified for a layer, its value will be * determined on startup in initResolutions() based on whether or not * any scale-specific properties have been set as options on the * layer. If no scale-specific options have been set on the layer, we * assume that it should always be in range. * * See #987 for more info. */ alwaysInRange: null, /** * Constant: RESOLUTION_PROPERTIES * {Array} The properties that are used for calculating resolutions * information. */ RESOLUTION_PROPERTIES: [ 'scales', 'resolutions', 'maxScale', 'minScale', 'maxResolution', 'minResolution', 'numZoomLevels', 'maxZoomLevel' ], /** * APIProperty: events * {} * * Register a listener for a particular event with the following syntax: * (code) * layer.events.register(type, obj, listener); * (end) * * Listeners will be called with a reference to an event object. The * properties of this event depends on exactly what happened. * * All event objects have at least the following properties: * object - {Object} A reference to layer.events.object. * element - {DOMElement} A reference to layer.events.element. * * Supported map event types: * loadstart - Triggered when layer loading starts. When using a Vector * layer with a Fixed or BBOX strategy, the event object includes * a *filter* property holding the OpenLayers.Filter used when * calling read on the protocol. * loadend - Triggered when layer loading ends. When using a Vector layer * with a Fixed or BBOX strategy, the event object includes a * *response* property holding an OpenLayers.Protocol.Response object. * visibilitychanged - Triggered when the layer's visibility property is * changed, e.g. by turning the layer on or off in the layer switcher. * Note that the actual visibility of the layer can also change if it * gets out of range (see ). If you also want to catch * these cases, register for the map's 'changelayer' event instead. * move - Triggered when layer moves (triggered with every mousemove * during a drag). * moveend - Triggered when layer is done moving, object passed as * argument has a zoomChanged boolean property which tells that the * zoom has changed. * added - Triggered after the layer is added to a map. Listeners will * receive an object with a *map* property referencing the map and a * *layer* property referencing the layer. * removed - Triggered after the layer is removed from the map. Listeners * will receive an object with a *map* property referencing the map and * a *layer* property referencing the layer. */ events: null, /** * APIProperty: map * {} This variable is set when the layer is added to * the map, via the accessor function setMap(). */ map: null, /** * APIProperty: isBaseLayer * {Boolean} Whether or not the layer is a base layer. This should be set * individually by all subclasses. Default is false */ isBaseLayer: false, /** * Property: alpha * {Boolean} The layer's images have an alpha channel. Default is false. */ alpha: false, /** * APIProperty: displayInLayerSwitcher * {Boolean} Display the layer's name in the layer switcher. Default is * true. */ displayInLayerSwitcher: true, /** * APIProperty: visibility * {Boolean} The layer should be displayed in the map. Default is true. */ visibility: true, /** * APIProperty: attribution * {String} Attribution string, displayed when an * has been added to the map. */ attribution: null, /** * Property: inRange * {Boolean} The current map resolution is within the layer's min/max * range. This is set in whenever the zoom * changes. */ inRange: false, /** * Propery: imageSize * {} For layers with a gutter, the image is larger than * the tile by twice the gutter in each dimension. */ imageSize: null, // OPTIONS /** * Property: options * {Object} An optional object whose properties will be set on the layer. * Any of the layer properties can be set as a property of the options * object and sent to the constructor when the layer is created. */ options: null, /** * APIProperty: eventListeners * {Object} If set as an option at construction, the eventListeners * object will be registered with . Object * structure must be a listeners object as shown in the example for * the events.on method. */ eventListeners: null, /** * APIProperty: gutter * {Integer} Determines the width (in pixels) of the gutter around image * tiles to ignore. By setting this property to a non-zero value, * images will be requested that are wider and taller than the tile * size by a value of 2 x gutter. This allows artifacts of rendering * at tile edges to be ignored. Set a gutter value that is equal to * half the size of the widest symbol that needs to be displayed. * Defaults to zero. Non-tiled layers always have zero gutter. */ gutter: 0, /** * APIProperty: projection * {} or {} Specifies the projection of the layer. * Can be set in the layer options. If not specified in the layer options, * it is set to the default projection specified in the map, * when the layer is added to the map. * Projection along with default maxExtent and resolutions * are set automatically with commercial baselayers in EPSG:3857, * such as Google, Bing and OpenStreetMap, and do not need to be specified. * Otherwise, if specifying projection, also set maxExtent, * maxResolution or resolutions as appropriate. * When using vector layers with strategies, layer projection should be set * to the projection of the source data if that is different from the map default. * * Can be either a string or an object; * if a string is passed, will be converted to an object when * the layer is added to the map. * */ projection: null, /** * APIProperty: units * {String} The layer map units. Defaults to null. Possible values * are 'degrees' (or 'dd'), 'm', 'ft', 'km', 'mi', 'inches'. * Normally taken from the projection. * Only required if both map and layers do not define a projection, * or if they define a projection which does not define units. */ units: null, /** * APIProperty: scales * {Array} An array of map scales in descending order. The values in the * array correspond to the map scale denominator. Note that these * values only make sense if the display (monitor) resolution of the * client is correctly guessed by whomever is configuring the * application. In addition, the units property must also be set. * Use instead wherever possible. */ scales: null, /** * APIProperty: resolutions * {Array} A list of map resolutions (map units per pixel) in descending * order. If this is not set in the layer constructor, it will be set * based on other resolution related properties (maxExtent, * maxResolution, maxScale, etc.). */ resolutions: null, /** * APIProperty: maxExtent * {|Array} If provided as an array, the array * should consist of four values (left, bottom, right, top). * The maximum extent for the layer. Defaults to null. * * The center of these bounds will not stray outside * of the viewport extent during panning. In addition, if * is set to false, data will not be * requested that falls completely outside of these bounds. */ maxExtent: null, /** * APIProperty: minExtent * {|Array} If provided as an array, the array * should consist of four values (left, bottom, right, top). * The minimum extent for the layer. Defaults to null. */ minExtent: null, /** * APIProperty: maxResolution * {Float} Default max is 360 deg / 256 px, which corresponds to * zoom level 0 on gmaps. Specify a different value in the layer * options if you are not using the default * and displaying the whole world. */ maxResolution: null, /** * APIProperty: minResolution * {Float} */ minResolution: null, /** * APIProperty: numZoomLevels * {Integer} */ numZoomLevels: null, /** * APIProperty: minScale * {Float} */ minScale: null, /** * APIProperty: maxScale * {Float} */ maxScale: null, /** * APIProperty: displayOutsideMaxExtent * {Boolean} Request map tiles that are completely outside of the max * extent for this layer. Defaults to false. */ displayOutsideMaxExtent: false, /** * APIProperty: wrapDateLine * {Boolean} Wraps the world at the international dateline, so the map can * be panned infinitely in longitudinal direction. Only use this on the * base layer, and only if the layer's maxExtent equals the world bounds. * #487 for more info. */ wrapDateLine: false, /** * Property: metadata * {Object} This object can be used to store additional information on a * layer object. */ metadata: null, /** * Constructor: OpenLayers.Layer * * Parameters: * name - {String} The layer name * options - {Object} Hashtable of extra options to tag onto the layer */ initialize: function(name, options) { this.metadata = {}; options = OpenLayers.Util.extend({}, options); // make sure we respect alwaysInRange if set on the prototype if (this.alwaysInRange != null) { options.alwaysInRange = this.alwaysInRange; } this.addOptions(options); this.name = name; if (this.id == null) { this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_"); this.div = OpenLayers.Util.createDiv(this.id); this.div.style.width = "100%"; this.div.style.height = "100%"; this.div.dir = "ltr"; this.events = new OpenLayers.Events(this, this.div); if(this.eventListeners instanceof Object) { this.events.on(this.eventListeners); } } }, /** * Method: destroy * Destroy is a destructor: this is to alleviate cyclic references which * the Javascript garbage cleaner can not take care of on its own. * * Parameters: * setNewBaseLayer - {Boolean} Set a new base layer when this layer has * been destroyed. Default is true. */ destroy: function(setNewBaseLayer) { if (setNewBaseLayer == null) { setNewBaseLayer = true; } if (this.map != null) { this.map.removeLayer(this, setNewBaseLayer); } this.projection = null; this.map = null; this.name = null; this.div = null; this.options = null; if (this.events) { if(this.eventListeners) { this.events.un(this.eventListeners); } this.events.destroy(); } this.eventListeners = null; this.events = null; }, /** * Method: clone * * Parameters: * obj - {} The layer to be cloned * * Returns: * {} An exact clone of this */ clone: function (obj) { if (obj == null) { obj = new OpenLayers.Layer(this.name, this.getOptions()); } // catch any randomly tagged-on properties OpenLayers.Util.applyDefaults(obj, this); // a cloned layer should never have its map property set // because it has not been added to a map yet. obj.map = null; return obj; }, /** * Method: getOptions * Extracts an object from the layer with the properties that were set as * options, but updates them with the values currently set on the * instance. * * Returns: * {Object} the of the layer, representing the current state. */ getOptions: function() { var options = {}; for(var o in this.options) { options[o] = this[o]; } return options; }, /** * APIMethod: setName * Sets the new layer name for this layer. Can trigger a changelayer event * on the map. * * Parameters: * newName - {String} The new name. */ setName: function(newName) { if (newName != this.name) { this.name = newName; if (this.map != null) { this.map.events.triggerEvent("changelayer", { layer: this, property: "name" }); } } }, /** * APIMethod: addOptions * * Parameters: * newOptions - {Object} * reinitialize - {Boolean} If set to true, and if resolution options of the * current baseLayer were changed, the map will be recentered to make * sure that it is displayed with a valid resolution, and a * changebaselayer event will be triggered. */ addOptions: function (newOptions, reinitialize) { if (this.options == null) { this.options = {}; } if (newOptions) { // make sure this.projection references a projection object if(typeof newOptions.projection == "string") { newOptions.projection = new OpenLayers.Projection(newOptions.projection); } if (newOptions.projection) { // get maxResolution, units and maxExtent from projection defaults if // they are not defined already OpenLayers.Util.applyDefaults(newOptions, OpenLayers.Projection.defaults[newOptions.projection.getCode()]); } // allow array for extents if (newOptions.maxExtent && !(newOptions.maxExtent instanceof OpenLayers.Bounds)) { newOptions.maxExtent = new OpenLayers.Bounds(newOptions.maxExtent); } if (newOptions.minExtent && !(newOptions.minExtent instanceof OpenLayers.Bounds)) { newOptions.minExtent = new OpenLayers.Bounds(newOptions.minExtent); } } // update our copy for clone OpenLayers.Util.extend(this.options, newOptions); // add new options to this OpenLayers.Util.extend(this, newOptions); // get the units from the projection, if we have a projection // and it it has units if(this.projection && this.projection.getUnits()) { this.units = this.projection.getUnits(); } // re-initialize resolutions if necessary, i.e. if any of the // properties of the "properties" array defined below is set // in the new options if(this.map) { // store current resolution so we can try to restore it later var resolution = this.map.getResolution(); var properties = this.RESOLUTION_PROPERTIES.concat( ["projection", "units", "minExtent", "maxExtent"] ); for(var o in newOptions) { if(newOptions.hasOwnProperty(o) && OpenLayers.Util.indexOf(properties, o) >= 0) { this.initResolutions(); if (reinitialize && this.map.baseLayer === this) { // update map position, and restore previous resolution this.map.setCenter(this.map.getCenter(), this.map.getZoomForResolution(resolution), false, true ); // trigger a changebaselayer event to make sure that // all controls (especially // OpenLayers.Control.PanZoomBar) get notified of the // new options this.map.events.triggerEvent("changebaselayer", { layer: this }); } break; } } } }, /** * APIMethod: onMapResize * This function can be implemented by subclasses */ onMapResize: function() { //this function can be implemented by subclasses }, /** * APIMethod: redraw * Redraws the layer. Returns true if the layer was redrawn, false if not. * * Returns: * {Boolean} The layer was redrawn. */ redraw: function() { var redrawn = false; if (this.map) { // min/max Range may have changed this.inRange = this.calculateInRange(); // map's center might not yet be set var extent = this.getExtent(); if (extent && this.inRange && this.visibility) { var zoomChanged = true; this.moveTo(extent, zoomChanged, false); this.events.triggerEvent("moveend", {"zoomChanged": zoomChanged}); redrawn = true; } } return redrawn; }, /** * Method: moveTo * * Parameters: * bounds - {} * zoomChanged - {Boolean} Tells when zoom has changed, as layers have to * do some init work in that case. * dragging - {Boolean} */ moveTo:function(bounds, zoomChanged, dragging) { var display = this.visibility; if (!this.isBaseLayer) { display = display && this.inRange; } this.display(display); }, /** * Method: moveByPx * Move the layer based on pixel vector. To be implemented by subclasses. * * Parameters: * dx - {Number} The x coord of the displacement vector. * dy - {Number} The y coord of the displacement vector. */ moveByPx: function(dx, dy) { }, /** * Method: setMap * Set the map property for the layer. This is done through an accessor * so that subclasses can override this and take special action once * they have their map variable set. * * Here we take care to bring over any of the necessary default * properties from the map. * * Parameters: * map - {} */ setMap: function(map) { if (this.map == null) { this.map = map; // grab some essential layer data from the map if it hasn't already // been set this.maxExtent = this.maxExtent || this.map.maxExtent; this.minExtent = this.minExtent || this.map.minExtent; this.projection = this.projection || this.map.projection; if (typeof this.projection == "string") { this.projection = new OpenLayers.Projection(this.projection); } // Check the projection to see if we can get units -- if not, refer // to properties. this.units = this.projection.getUnits() || this.units || this.map.units; this.initResolutions(); if (!this.isBaseLayer) { this.inRange = this.calculateInRange(); var show = ((this.visibility) && (this.inRange)); this.div.style.display = show ? "" : "none"; } // deal with gutters this.setTileSize(); } }, /** * Method: afterAdd * Called at the end of the map.addLayer sequence. At this point, the map * will have a base layer. To be overridden by subclasses. */ afterAdd: function() { }, /** * APIMethod: removeMap * Just as setMap() allows each layer the possibility to take a * personalized action on being added to the map, removeMap() allows * each layer to take a personalized action on being removed from it. * For now, this will be mostly unused, except for the EventPane layer, * which needs this hook so that it can remove the special invisible * pane. * * Parameters: * map - {} */ removeMap: function(map) { //to be overridden by subclasses }, /** * APIMethod: getImageSize * * Parameters: * bounds - {} optional tile bounds, can be used * by subclasses that have to deal with different tile sizes at the * layer extent edges (e.g. Zoomify) * * Returns: * {} The size that the image should be, taking into * account gutters. */ getImageSize: function(bounds) { return (this.imageSize || this.tileSize); }, /** * APIMethod: setTileSize * Set the tile size based on the map size. This also sets layer.imageSize * or use by Tile.Image. * * Parameters: * size - {} */ setTileSize: function(size) { var tileSize = (size) ? size : ((this.tileSize) ? this.tileSize : this.map.getTileSize()); this.tileSize = tileSize; if(this.gutter) { // layers with gutters need non-null tile sizes //if(tileSize == null) { // OpenLayers.console.error("Error in layer.setMap() for " + // this.name + ": layers with " + // "gutters need non-null tile sizes"); //} this.imageSize = new OpenLayers.Size(tileSize.w + (2*this.gutter), tileSize.h + (2*this.gutter)); } }, /** * APIMethod: getVisibility * * Returns: * {Boolean} The layer should be displayed (if in range). */ getVisibility: function() { return this.visibility; }, /** * APIMethod: setVisibility * Set the visibility flag for the layer and hide/show & redraw * accordingly. Fire event unless otherwise specified * * Note that visibility is no longer simply whether or not the layer's * style.display is set to "block". Now we store a 'visibility' state * property on the layer class, this allows us to remember whether or * not we *desire* for a layer to be visible. In the case where the * map's resolution is out of the layer's range, this desire may be * subverted. * * Parameters: * visibility - {Boolean} Whether or not to display the layer (if in range) */ setVisibility: function(visibility) { if (visibility != this.visibility) { this.visibility = visibility; this.display(visibility); this.redraw(); if (this.map != null) { this.map.events.triggerEvent("changelayer", { layer: this, property: "visibility" }); } this.events.triggerEvent("visibilitychanged"); } }, /** * APIMethod: display * Hide or show the Layer. This is designed to be used internally, and * is not generally the way to enable or disable the layer. For that, * use the setVisibility function instead.. * * Parameters: * display - {Boolean} */ display: function(display) { if (display != (this.div.style.display != "none")) { this.div.style.display = (display && this.calculateInRange()) ? "block" : "none"; } }, /** * APIMethod: calculateInRange * * Returns: * {Boolean} The layer is displayable at the current map's current * resolution. Note that if 'alwaysInRange' is true for the layer, * this function will always return true. */ calculateInRange: function() { var inRange = false; if (this.alwaysInRange) { inRange = true; } else { if (this.map) { var resolution = this.map.getResolution(); inRange = ( (resolution >= this.minResolution) && (resolution <= this.maxResolution) ); } } return inRange; }, /** * APIMethod: setIsBaseLayer * * Parameters: * isBaseLayer - {Boolean} */ setIsBaseLayer: function(isBaseLayer) { if (isBaseLayer != this.isBaseLayer) { this.isBaseLayer = isBaseLayer; if (this.map != null) { this.map.events.triggerEvent("changebaselayer", { layer: this }); } } }, /********************************************************/ /* */ /* Baselayer Functions */ /* */ /********************************************************/ /** * Method: initResolutions * This method's responsibility is to set up the 'resolutions' array * for the layer -- this array is what the layer will use to interface * between the zoom levels of the map and the resolution display * of the layer. * * The user has several options that determine how the array is set up. * * For a detailed explanation, see the following wiki from the * openlayers.org homepage: * http://trac.openlayers.org/wiki/SettingZoomLevels */ initResolutions: function() { // ok we want resolutions, here's our strategy: // // 1. if resolutions are defined in the layer config, use them // 2. else, if scales are defined in the layer config then derive // resolutions from these scales // 3. else, attempt to calculate resolutions from maxResolution, // minResolution, numZoomLevels, maxZoomLevel set in the // layer config // 4. if we still don't have resolutions, and if resolutions // are defined in the same, use them // 5. else, if scales are defined in the map then derive // resolutions from these scales // 6. else, attempt to calculate resolutions from maxResolution, // minResolution, numZoomLevels, maxZoomLevel set in the // map // 7. hope for the best! var i, len, p; var props = {}, alwaysInRange = true; // get resolution data from layer config // (we also set alwaysInRange in the layer as appropriate) for(i=0, len=this.RESOLUTION_PROPERTIES.length; i} A Bounds object which represents the lon/lat * bounds of the current viewPort. */ getExtent: function() { // just use stock map calculateBounds function -- passing no arguments // means it will user map's current center & resolution // return this.map.calculateBounds(); }, /** * APIMethod: getZoomForExtent * * Parameters: * extent - {} * closest - {Boolean} Find the zoom level that most closely fits the * specified bounds. Note that this may result in a zoom that does * not exactly contain the entire extent. * Default is false. * * Returns: * {Integer} The index of the zoomLevel (entry in the resolutions array) * for the passed-in extent. We do this by calculating the ideal * resolution for the given extent (based on the map size) and then * calling getZoomForResolution(), passing along the 'closest' * parameter. */ getZoomForExtent: function(extent, closest) { var viewSize = this.map.getSize(); var idealResolution = Math.max( extent.getWidth() / viewSize.w, extent.getHeight() / viewSize.h ); return this.getZoomForResolution(idealResolution, closest); }, /** * Method: getDataExtent * Calculates the max extent which includes all of the data for the layer. * This function is to be implemented by subclasses. * * Returns: * {} */ getDataExtent: function () { //to be implemented by subclasses }, /** * APIMethod: getResolutionForZoom * * Parameters: * zoom - {Float} * * Returns: * {Float} A suitable resolution for the specified zoom. */ getResolutionForZoom: function(zoom) { zoom = Math.max(0, Math.min(zoom, this.resolutions.length - 1)); var resolution; if(this.map.fractionalZoom) { var low = Math.floor(zoom); var high = Math.ceil(zoom); resolution = this.resolutions[low] - ((zoom-low) * (this.resolutions[low]-this.resolutions[high])); } else { resolution = this.resolutions[Math.round(zoom)]; } return resolution; }, /** * APIMethod: getZoomForResolution * * Parameters: * resolution - {Float} * closest - {Boolean} Find the zoom level that corresponds to the absolute * closest resolution, which may result in a zoom whose corresponding * resolution is actually smaller than we would have desired (if this * is being called from a getZoomForExtent() call, then this means that * the returned zoom index might not actually contain the entire * extent specified... but it'll be close). * Default is false. * * Returns: * {Integer} The index of the zoomLevel (entry in the resolutions array) * that corresponds to the best fit resolution given the passed in * value and the 'closest' specification. */ getZoomForResolution: function(resolution, closest) { var zoom, i, len; if(this.map.fractionalZoom) { var lowZoom = 0; var highZoom = this.resolutions.length - 1; var highRes = this.resolutions[lowZoom]; var lowRes = this.resolutions[highZoom]; var res; for(i=0, len=this.resolutions.length; i= resolution) { highRes = res; lowZoom = i; } if(res <= resolution) { lowRes = res; highZoom = i; break; } } var dRes = highRes - lowRes; if(dRes > 0) { zoom = lowZoom + ((highRes - resolution) / dRes); } else { zoom = lowZoom; } } else { var diff; var minDiff = Number.POSITIVE_INFINITY; for(i=0, len=this.resolutions.length; i minDiff) { break; } minDiff = diff; } else { if (this.resolutions[i] < resolution) { break; } } } zoom = Math.max(0, i-1); } return zoom; }, /** * APIMethod: getLonLatFromViewPortPx * * Parameters: * viewPortPx - {|Object} An OpenLayers.Pixel or * an object with a 'x' * and 'y' properties. * * Returns: * {} An OpenLayers.LonLat which is the passed-in * view port , translated into lon/lat by the layer. */ getLonLatFromViewPortPx: function (viewPortPx) { var lonlat = null; var map = this.map; if (viewPortPx != null && map.minPx) { var res = map.getResolution(); var maxExtent = map.getMaxExtent({restricted: true}); var lon = (viewPortPx.x - map.minPx.x) * res + maxExtent.left; var lat = (map.minPx.y - viewPortPx.y) * res + maxExtent.top; lonlat = new OpenLayers.LonLat(lon, lat); if (this.wrapDateLine) { lonlat = lonlat.wrapDateLine(this.maxExtent); } } return lonlat; }, /** * APIMethod: getViewPortPxFromLonLat * Returns a pixel location given a map location. This method will return * fractional pixel values. * * Parameters: * lonlat - {|Object} An OpenLayers.LonLat or * an object with a 'lon' * and 'lat' properties. * * Returns: * {} An which is the passed-in * lonlat translated into view port pixels. */ getViewPortPxFromLonLat: function (lonlat, resolution) { var px = null; if (lonlat != null) { resolution = resolution || this.map.getResolution(); var extent = this.map.calculateBounds(null, resolution); px = new OpenLayers.Pixel( (1/resolution * (lonlat.lon - extent.left)), (1/resolution * (extent.top - lonlat.lat)) ); } return px; }, /** * APIMethod: setOpacity * Sets the opacity for the entire layer (all images) * * Parameters: * opacity - {Float} */ setOpacity: function(opacity) { if (opacity != this.opacity) { this.opacity = opacity; var childNodes = this.div.childNodes; for(var i = 0, len = childNodes.length; i < len; ++i) { var element = childNodes[i].firstChild || childNodes[i]; var lastChild = childNodes[i].lastChild; //TODO de-uglify this if (lastChild && lastChild.nodeName.toLowerCase() === "iframe") { element = lastChild.parentNode; } OpenLayers.Util.modifyDOMElement(element, null, null, null, null, null, null, opacity); } if (this.map != null) { this.map.events.triggerEvent("changelayer", { layer: this, property: "opacity" }); } } }, /** * Method: getZIndex * * Returns: * {Integer} the z-index of this layer */ getZIndex: function () { return this.div.style.zIndex; }, /** * Method: setZIndex * * Parameters: * zIndex - {Integer} */ setZIndex: function (zIndex) { this.div.style.zIndex = zIndex; }, /** * Method: adjustBounds * This function will take a bounds, and if wrapDateLine option is set * on the layer, it will return a bounds which is wrapped around the * world. We do not wrap for bounds which *cross* the * maxExtent.left/right, only bounds which are entirely to the left * or entirely to the right. * * Parameters: * bounds - {} */ adjustBounds: function (bounds) { if (this.gutter) { // Adjust the extent of a bounds in map units by the // layer's gutter in pixels. var mapGutter = this.gutter * this.map.getResolution(); bounds = new OpenLayers.Bounds(bounds.left - mapGutter, bounds.bottom - mapGutter, bounds.right + mapGutter, bounds.top + mapGutter); } if (this.wrapDateLine) { // wrap around the date line, within the limits of rounding error var wrappingOptions = { 'rightTolerance':this.getResolution(), 'leftTolerance':this.getResolution() }; bounds = bounds.wrapDateLine(this.maxExtent, wrappingOptions); } return bounds; }, CLASS_NAME: "OpenLayers.Layer" }); /* ====================================================================== OpenLayers/Layer/SphericalMercator.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Layer.js * @requires OpenLayers/Projection.js */ /** * Class: OpenLayers.Layer.SphericalMercator * A mixin for layers that wraps up the pieces neccesary to have a coordinate * conversion for working with commercial APIs which use a spherical * mercator projection. Using this layer as a base layer, additional * layers can be used as overlays if they are in the same projection. * * A layer is given properties of this object by setting the sphericalMercator * property to true. * * More projection information: * - http://spatialreference.org/ref/user/google-projection/ * * Proj4 Text: * +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 * +k=1.0 +units=m +nadgrids=@null +no_defs * * WKT: * 900913=PROJCS["WGS84 / Simple Mercator", GEOGCS["WGS 84", * DATUM["WGS_1984", SPHEROID["WGS_1984", 6378137.0, 298.257223563]], * PRIMEM["Greenwich", 0.0], UNIT["degree", 0.017453292519943295], * AXIS["Longitude", EAST], AXIS["Latitude", NORTH]], * PROJECTION["Mercator_1SP_Google"], * PARAMETER["latitude_of_origin", 0.0], PARAMETER["central_meridian", 0.0], * PARAMETER["scale_factor", 1.0], PARAMETER["false_easting", 0.0], * PARAMETER["false_northing", 0.0], UNIT["m", 1.0], AXIS["x", EAST], * AXIS["y", NORTH], AUTHORITY["EPSG","900913"]] */ OpenLayers.Layer.SphericalMercator = { /** * Method: getExtent * Get the map's extent. * * Returns: * {} The map extent. */ getExtent: function() { var extent = null; if (this.sphericalMercator) { extent = this.map.calculateBounds(); } else { extent = OpenLayers.Layer.FixedZoomLevels.prototype.getExtent.apply(this); } return extent; }, /** * Method: getLonLatFromViewPortPx * Get a map location from a pixel location * * Parameters: * viewPortPx - {} * * Returns: * {} An OpenLayers.LonLat which is the passed-in view * port OpenLayers.Pixel, translated into lon/lat by map lib * If the map lib is not loaded or not centered, returns null */ getLonLatFromViewPortPx: function (viewPortPx) { return OpenLayers.Layer.prototype.getLonLatFromViewPortPx.apply(this, arguments); }, /** * Method: getViewPortPxFromLonLat * Get a pixel location from a map location * * Parameters: * lonlat - {} * * Returns: * {} An OpenLayers.Pixel which is the passed-in * OpenLayers.LonLat, translated into view port pixels by map lib * If map lib is not loaded or not centered, returns null */ getViewPortPxFromLonLat: function (lonlat) { return OpenLayers.Layer.prototype.getViewPortPxFromLonLat.apply(this, arguments); }, /** * Method: initMercatorParameters * Set up the mercator parameters on the layer: resolutions, * projection, units. */ initMercatorParameters: function() { // set up properties for Mercator - assume EPSG:900913 this.RESOLUTIONS = []; var maxResolution = 156543.03390625; for(var zoom=0; zoom<=this.MAX_ZOOM_LEVEL; ++zoom) { this.RESOLUTIONS[zoom] = maxResolution / Math.pow(2, zoom); } this.units = "m"; this.projection = this.projection || "EPSG:900913"; }, /** * APIMethod: forwardMercator * Given a lon,lat in EPSG:4326, return a point in Spherical Mercator. * * Parameters: * lon - {float} * lat - {float} * * Returns: * {} The coordinates transformed to Mercator. */ forwardMercator: (function() { var gg = new OpenLayers.Projection("EPSG:4326"); var sm = new OpenLayers.Projection("EPSG:900913"); return function(lon, lat) { var point = OpenLayers.Projection.transform({x: lon, y: lat}, gg, sm); return new OpenLayers.LonLat(point.x, point.y); }; })(), /** * APIMethod: inverseMercator * Given a x,y in Spherical Mercator, return a point in EPSG:4326. * * Parameters: * x - {float} A map x in Spherical Mercator. * y - {float} A map y in Spherical Mercator. * * Returns: * {} The coordinates transformed to EPSG:4326. */ inverseMercator: (function() { var gg = new OpenLayers.Projection("EPSG:4326"); var sm = new OpenLayers.Projection("EPSG:900913"); return function(x, y) { var point = OpenLayers.Projection.transform({x: x, y: y}, sm, gg); return new OpenLayers.LonLat(point.x, point.y); }; })() }; /* ====================================================================== OpenLayers/Layer/EventPane.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Layer.js * @requires OpenLayers/Util.js */ /** * Class: OpenLayers.Layer.EventPane * Base class for 3rd party layers, providing a DOM element which isolates * the 3rd-party layer from mouse events. * Only used by Google layers. * * Automatically instantiated by the Google constructor, and not usually instantiated directly. * * Create a new event pane layer with the * constructor. * * Inherits from: * - */ OpenLayers.Layer.EventPane = OpenLayers.Class(OpenLayers.Layer, { /** * APIProperty: smoothDragPan * {Boolean} smoothDragPan determines whether non-public/internal API * methods are used for better performance while dragging EventPane * layers. When not in sphericalMercator mode, the smoother dragging * doesn't actually move north/south directly with the number of * pixels moved, resulting in a slight offset when you drag your mouse * north south with this option on. If this visual disparity bothers * you, you should turn this option off, or use spherical mercator. * Default is on. */ smoothDragPan: true, /** * Property: isBaseLayer * {Boolean} EventPaned layers are always base layers, by necessity. */ isBaseLayer: true, /** * APIProperty: isFixed * {Boolean} EventPaned layers are fixed by default. */ isFixed: true, /** * Property: pane * {DOMElement} A reference to the element that controls the events. */ pane: null, /** * Property: mapObject * {Object} This is the object which will be used to load the 3rd party library * in the case of the google layer, this will be of type GMap, * in the case of the ve layer, this will be of type VEMap */ mapObject: null, /** * Constructor: OpenLayers.Layer.EventPane * Create a new event pane layer * * Parameters: * name - {String} * options - {Object} Hashtable of extra options to tag onto the layer */ initialize: function(name, options) { OpenLayers.Layer.prototype.initialize.apply(this, arguments); if (this.pane == null) { this.pane = OpenLayers.Util.createDiv(this.div.id + "_EventPane"); } }, /** * APIMethod: destroy * Deconstruct this layer. */ destroy: function() { this.mapObject = null; this.pane = null; OpenLayers.Layer.prototype.destroy.apply(this, arguments); }, /** * Method: setMap * Set the map property for the layer. This is done through an accessor * so that subclasses can override this and take special action once * they have their map variable set. * * Parameters: * map - {} */ setMap: function(map) { OpenLayers.Layer.prototype.setMap.apply(this, arguments); this.pane.style.zIndex = parseInt(this.div.style.zIndex) + 1; this.pane.style.display = this.div.style.display; this.pane.style.width="100%"; this.pane.style.height="100%"; if (OpenLayers.BROWSER_NAME == "msie") { this.pane.style.background = "url(" + OpenLayers.Util.getImageLocation("blank.gif") + ")"; } if (this.isFixed) { this.map.viewPortDiv.appendChild(this.pane); } else { this.map.layerContainerDiv.appendChild(this.pane); } // once our layer has been added to the map, we can load it this.loadMapObject(); // if map didn't load, display warning if (this.mapObject == null) { this.loadWarningMessage(); } }, /** * APIMethod: removeMap * On being removed from the map, we'll like to remove the invisible 'pane' * div that we added to it on creation. * * Parameters: * map - {} */ removeMap: function(map) { if (this.pane && this.pane.parentNode) { this.pane.parentNode.removeChild(this.pane); } OpenLayers.Layer.prototype.removeMap.apply(this, arguments); }, /** * Method: loadWarningMessage * If we can't load the map lib, then display an error message to the * user and tell them where to go for help. * * This function sets up the layout for the warning message. Each 3rd * party layer must implement its own getWarningHTML() function to * provide the actual warning message. */ loadWarningMessage:function() { this.div.style.backgroundColor = "darkblue"; var viewSize = this.map.getSize(); var msgW = Math.min(viewSize.w, 300); var msgH = Math.min(viewSize.h, 200); var size = new OpenLayers.Size(msgW, msgH); var centerPx = new OpenLayers.Pixel(viewSize.w/2, viewSize.h/2); var topLeft = centerPx.add(-size.w/2, -size.h/2); var div = OpenLayers.Util.createDiv(this.name + "_warning", topLeft, size, null, null, null, "auto"); div.style.padding = "7px"; div.style.backgroundColor = "yellow"; div.innerHTML = this.getWarningHTML(); this.div.appendChild(div); }, /** * Method: getWarningHTML * To be implemented by subclasses. * * Returns: * {String} String with information on why layer is broken, how to get * it working. */ getWarningHTML:function() { //should be implemented by subclasses return ""; }, /** * Method: display * Set the display on the pane * * Parameters: * display - {Boolean} */ display: function(display) { OpenLayers.Layer.prototype.display.apply(this, arguments); this.pane.style.display = this.div.style.display; }, /** * Method: setZIndex * Set the z-index order for the pane. * * Parameters: * zIndex - {int} */ setZIndex: function (zIndex) { OpenLayers.Layer.prototype.setZIndex.apply(this, arguments); this.pane.style.zIndex = parseInt(this.div.style.zIndex) + 1; }, /** * Method: moveByPx * Move the layer based on pixel vector. To be implemented by subclasses. * * Parameters: * dx - {Number} The x coord of the displacement vector. * dy - {Number} The y coord of the displacement vector. */ moveByPx: function(dx, dy) { OpenLayers.Layer.prototype.moveByPx.apply(this, arguments); if (this.dragPanMapObject) { this.dragPanMapObject(dx, -dy); } else { this.moveTo(this.map.getCachedCenter()); } }, /** * Method: moveTo * Handle calls to move the layer. * * Parameters: * bounds - {} * zoomChanged - {Boolean} * dragging - {Boolean} */ moveTo:function(bounds, zoomChanged, dragging) { OpenLayers.Layer.prototype.moveTo.apply(this, arguments); if (this.mapObject != null) { var newCenter = this.map.getCenter(); var newZoom = this.map.getZoom(); if (newCenter != null) { var moOldCenter = this.getMapObjectCenter(); var oldCenter = this.getOLLonLatFromMapObjectLonLat(moOldCenter); var moOldZoom = this.getMapObjectZoom(); var oldZoom= this.getOLZoomFromMapObjectZoom(moOldZoom); if (!(newCenter.equals(oldCenter)) || newZoom != oldZoom) { if (!zoomChanged && oldCenter && this.dragPanMapObject && this.smoothDragPan) { var oldPx = this.map.getViewPortPxFromLonLat(oldCenter); var newPx = this.map.getViewPortPxFromLonLat(newCenter); this.dragPanMapObject(newPx.x-oldPx.x, oldPx.y-newPx.y); } else { var center = this.getMapObjectLonLatFromOLLonLat(newCenter); var zoom = this.getMapObjectZoomFromOLZoom(newZoom); this.setMapObjectCenter(center, zoom, dragging); } } } } }, /********************************************************/ /* */ /* Baselayer Functions */ /* */ /********************************************************/ /** * Method: getLonLatFromViewPortPx * Get a map location from a pixel location * * Parameters: * viewPortPx - {} * * Returns: * {} An OpenLayers.LonLat which is the passed-in view * port OpenLayers.Pixel, translated into lon/lat by map lib * If the map lib is not loaded or not centered, returns null */ getLonLatFromViewPortPx: function (viewPortPx) { var lonlat = null; if ( (this.mapObject != null) && (this.getMapObjectCenter() != null) ) { var moPixel = this.getMapObjectPixelFromOLPixel(viewPortPx); var moLonLat = this.getMapObjectLonLatFromMapObjectPixel(moPixel); lonlat = this.getOLLonLatFromMapObjectLonLat(moLonLat); } return lonlat; }, /** * Method: getViewPortPxFromLonLat * Get a pixel location from a map location * * Parameters: * lonlat - {} * * Returns: * {} An OpenLayers.Pixel which is the passed-in * OpenLayers.LonLat, translated into view port pixels by map lib * If map lib is not loaded or not centered, returns null */ getViewPortPxFromLonLat: function (lonlat) { var viewPortPx = null; if ( (this.mapObject != null) && (this.getMapObjectCenter() != null) ) { var moLonLat = this.getMapObjectLonLatFromOLLonLat(lonlat); var moPixel = this.getMapObjectPixelFromMapObjectLonLat(moLonLat); viewPortPx = this.getOLPixelFromMapObjectPixel(moPixel); } return viewPortPx; }, /********************************************************/ /* */ /* Translation Functions */ /* */ /* The following functions translate Map Object and */ /* OL formats for Pixel, LonLat */ /* */ /********************************************************/ // // TRANSLATION: MapObject LatLng <-> OpenLayers.LonLat // /** * Method: getOLLonLatFromMapObjectLonLat * Get an OL style map location from a 3rd party style map location * * Parameters * moLonLat - {Object} * * Returns: * {} An OpenLayers.LonLat, translated from the passed in * MapObject LonLat * Returns null if null value is passed in */ getOLLonLatFromMapObjectLonLat: function(moLonLat) { var olLonLat = null; if (moLonLat != null) { var lon = this.getLongitudeFromMapObjectLonLat(moLonLat); var lat = this.getLatitudeFromMapObjectLonLat(moLonLat); olLonLat = new OpenLayers.LonLat(lon, lat); } return olLonLat; }, /** * Method: getMapObjectLonLatFromOLLonLat * Get a 3rd party map location from an OL map location. * * Parameters: * olLonLat - {} * * Returns: * {Object} A MapObject LonLat, translated from the passed in * OpenLayers.LonLat * Returns null if null value is passed in */ getMapObjectLonLatFromOLLonLat: function(olLonLat) { var moLatLng = null; if (olLonLat != null) { moLatLng = this.getMapObjectLonLatFromLonLat(olLonLat.lon, olLonLat.lat); } return moLatLng; }, // // TRANSLATION: MapObject Pixel <-> OpenLayers.Pixel // /** * Method: getOLPixelFromMapObjectPixel * Get an OL pixel location from a 3rd party pixel location. * * Parameters: * moPixel - {Object} * * Returns: * {} An OpenLayers.Pixel, translated from the passed in * MapObject Pixel * Returns null if null value is passed in */ getOLPixelFromMapObjectPixel: function(moPixel) { var olPixel = null; if (moPixel != null) { var x = this.getXFromMapObjectPixel(moPixel); var y = this.getYFromMapObjectPixel(moPixel); olPixel = new OpenLayers.Pixel(x, y); } return olPixel; }, /** * Method: getMapObjectPixelFromOLPixel * Get a 3rd party pixel location from an OL pixel location * * Parameters: * olPixel - {} * * Returns: * {Object} A MapObject Pixel, translated from the passed in * OpenLayers.Pixel * Returns null if null value is passed in */ getMapObjectPixelFromOLPixel: function(olPixel) { var moPixel = null; if (olPixel != null) { moPixel = this.getMapObjectPixelFromXY(olPixel.x, olPixel.y); } return moPixel; }, CLASS_NAME: "OpenLayers.Layer.EventPane" }); /* ====================================================================== OpenLayers/Layer/FixedZoomLevels.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Layer.js */ /** * Class: OpenLayers.Layer.FixedZoomLevels * Some Layers will already have established zoom levels (like google * or ve). Instead of trying to determine them and populate a resolutions[] * Array with those values, we will hijack the resolution functionality * here. * * When you subclass FixedZoomLevels: * * The initResolutions() call gets nullified, meaning no resolutions[] array * is set up. Which would be a big problem getResolution() in Layer, since * it merely takes map.zoom and indexes into resolutions[]... but.... * * The getResolution() call is also overridden. Instead of using the * resolutions[] array, we simply calculate the current resolution based * on the current extent and the current map size. But how will we be able * to calculate the current extent without knowing the resolution...? * * The getExtent() function is also overridden. Instead of calculating extent * based on the center point and the current resolution, we instead * calculate the extent by getting the lonlats at the top-left and * bottom-right by using the getLonLatFromViewPortPx() translation function, * taken from the pixel locations (0,0) and the size of the map. But how * will we be able to do lonlat-px translation without resolution....? * * The getZoomForResolution() method is overridden. Instead of indexing into * the resolutions[] array, we call OpenLayers.Layer.getExent(), passing in * the desired resolution. With this extent, we then call getZoomForExtent() * * * Whenever you implement a layer using OpenLayers.Layer.FixedZoomLevels, * it is your responsibility to provide the following three functions: * * - getLonLatFromViewPortPx * - getViewPortPxFromLonLat * - getZoomForExtent * * ...those three functions should generally be provided by any reasonable * API that you might be working from. * */ OpenLayers.Layer.FixedZoomLevels = OpenLayers.Class({ /********************************************************/ /* */ /* Baselayer Functions */ /* */ /* The following functions must all be implemented */ /* by all base layers */ /* */ /********************************************************/ /** * Constructor: OpenLayers.Layer.FixedZoomLevels * Create a new fixed zoom levels layer. */ initialize: function() { //this class is only just to add the following functions... // nothing to actually do here... but it is probably a good // idea to have layers that use these functions call this // inititalize() anyways, in case at some point we decide we // do want to put some functionality or state in here. }, /** * Method: initResolutions * Populate the resolutions array */ initResolutions: function() { var props = ['minZoomLevel', 'maxZoomLevel', 'numZoomLevels']; for(var i=0, len=props.length; i lonlat translation functions on tl and br * corners of viewport * * Returns: * {} A Bounds object which represents the lon/lat * bounds of the current viewPort. */ getExtent: function () { var size = this.map.getSize(); var tl = this.getLonLatFromViewPortPx({ x: 0, y: 0 }); var br = this.getLonLatFromViewPortPx({ x: size.w, y: size.h }); if ((tl != null) && (br != null)) { return new OpenLayers.Bounds(tl.lon, br.lat, br.lon, tl.lat); } else { return null; } }, /** * Method: getZoomForResolution * Get the zoom level for a given resolution * * Parameters: * resolution - {Float} * * Returns: * {Integer} A suitable zoom level for the specified resolution. * If no baselayer is set, returns null. */ getZoomForResolution: function(resolution) { if (this.resolutions != null) { return OpenLayers.Layer.prototype.getZoomForResolution.apply(this, arguments); } else { var extent = OpenLayers.Layer.prototype.getExtent.apply(this, []); return this.getZoomForExtent(extent); } }, /********************************************************/ /* */ /* Translation Functions */ /* */ /* The following functions translate GMaps and OL */ /* formats for Pixel, LonLat, Bounds, and Zoom */ /* */ /********************************************************/ // // TRANSLATION: MapObject Zoom <-> OpenLayers Zoom // /** * Method: getOLZoomFromMapObjectZoom * Get the OL zoom index from the map object zoom level * * Parameters: * moZoom - {Integer} * * Returns: * {Integer} An OpenLayers Zoom level, translated from the passed in zoom * Returns null if null value is passed in */ getOLZoomFromMapObjectZoom: function(moZoom) { var zoom = null; if (moZoom != null) { zoom = moZoom - this.minZoomLevel; if (this.map.baseLayer !== this) { zoom = this.map.baseLayer.getZoomForResolution( this.getResolutionForZoom(zoom) ); } } return zoom; }, /** * Method: getMapObjectZoomFromOLZoom * Get the map object zoom level from the OL zoom level * * Parameters: * olZoom - {Integer} * * Returns: * {Integer} A MapObject level, translated from the passed in olZoom * Returns null if null value is passed in */ getMapObjectZoomFromOLZoom: function(olZoom) { var zoom = null; if (olZoom != null) { zoom = olZoom + this.minZoomLevel; if (this.map.baseLayer !== this) { zoom = this.getZoomForResolution( this.map.baseLayer.getResolutionForZoom(zoom) ); } } return zoom; }, CLASS_NAME: "OpenLayers.Layer.FixedZoomLevels" }); /* ====================================================================== OpenLayers/Layer/Google.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Layer/SphericalMercator.js * @requires OpenLayers/Layer/EventPane.js * @requires OpenLayers/Layer/FixedZoomLevels.js * @requires OpenLayers/Lang.js */ /** * Class: OpenLayers.Layer.Google * * Provides a wrapper for Google's Maps API * Normally the Terms of Use for this API do not allow wrapping, but Google * have provided written consent to OpenLayers for this - see email in * http://osgeo-org.1560.n6.nabble.com/Google-Maps-API-Terms-of-Use-changes-tp4910013p4911981.html * * Inherits from: * - * - * - */ OpenLayers.Layer.Google = OpenLayers.Class( OpenLayers.Layer.EventPane, OpenLayers.Layer.FixedZoomLevels, { /** * Constant: MIN_ZOOM_LEVEL * {Integer} 0 */ MIN_ZOOM_LEVEL: 0, /** * Constant: MAX_ZOOM_LEVEL * {Integer} 21 */ MAX_ZOOM_LEVEL: 21, /** * Constant: RESOLUTIONS * {Array(Float)} Hardcode these resolutions so that they are more closely * tied with the standard wms projection */ RESOLUTIONS: [ 1.40625, 0.703125, 0.3515625, 0.17578125, 0.087890625, 0.0439453125, 0.02197265625, 0.010986328125, 0.0054931640625, 0.00274658203125, 0.001373291015625, 0.0006866455078125, 0.00034332275390625, 0.000171661376953125, 0.0000858306884765625, 0.00004291534423828125, 0.00002145767211914062, 0.00001072883605957031, 0.00000536441802978515, 0.00000268220901489257, 0.0000013411045074462891, 0.00000067055225372314453 ], /** * APIProperty: type * {GMapType} */ type: null, /** * APIProperty: wrapDateLine * {Boolean} Allow user to pan forever east/west. Default is true. * Setting this to false only restricts panning if * is true. */ wrapDateLine: true, /** * APIProperty: sphericalMercator * {Boolean} Should the map act as a mercator-projected map? This will * cause all interactions with the map to be in the actual map * projection, which allows support for vector drawing, overlaying * other maps, etc. */ sphericalMercator: false, /** * Property: version * {Number} The version of the Google Maps API */ version: null, /** * Constructor: OpenLayers.Layer.Google * * Parameters: * name - {String} A name for the layer. * options - {Object} An optional object whose properties will be set * on the layer. */ initialize: function(name, options) { options = options || {}; if(!options.version) { options.version = typeof GMap2 === "function" ? "2" : "3"; } var mixin = OpenLayers.Layer.Google["v" + options.version.replace(/\./g, "_")]; if (mixin) { OpenLayers.Util.applyDefaults(options, mixin); } else { throw "Unsupported Google Maps API version: " + options.version; } OpenLayers.Util.applyDefaults(options, mixin.DEFAULTS); if (options.maxExtent) { options.maxExtent = options.maxExtent.clone(); } OpenLayers.Layer.EventPane.prototype.initialize.apply(this, [name, options]); OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this, [name, options]); if (this.sphericalMercator) { OpenLayers.Util.extend(this, OpenLayers.Layer.SphericalMercator); this.initMercatorParameters(); } }, /** * Method: clone * Create a clone of this layer * * Returns: * {} An exact clone of this layer */ clone: function() { /** * This method isn't intended to be called by a subclass and it * doesn't call the same method on the superclass. We don't call * the super's clone because we don't want properties that are set * on this layer after initialize (i.e. this.mapObject etc.). */ return new OpenLayers.Layer.Google( this.name, this.getOptions() ); }, /** * APIMethod: setVisibility * Set the visibility flag for the layer and hide/show & redraw * accordingly. Fire event unless otherwise specified * * Note that visibility is no longer simply whether or not the layer's * style.display is set to "block". Now we store a 'visibility' state * property on the layer class, this allows us to remember whether or * not we *desire* for a layer to be visible. In the case where the * map's resolution is out of the layer's range, this desire may be * subverted. * * Parameters: * visible - {Boolean} Display the layer (if in range) */ setVisibility: function(visible) { // sharing a map container, opacity has to be set per layer var opacity = this.opacity == null ? 1 : this.opacity; OpenLayers.Layer.EventPane.prototype.setVisibility.apply(this, arguments); this.setOpacity(opacity); }, /** * APIMethod: display * Hide or show the Layer * * Parameters: * visible - {Boolean} */ display: function(visible) { if (!this._dragging) { this.setGMapVisibility(visible); } OpenLayers.Layer.EventPane.prototype.display.apply(this, arguments); }, /** * Method: moveTo * * Parameters: * bounds - {} * zoomChanged - {Boolean} Tells when zoom has changed, as layers have to * do some init work in that case. * dragging - {Boolean} */ moveTo: function(bounds, zoomChanged, dragging) { this._dragging = dragging; OpenLayers.Layer.EventPane.prototype.moveTo.apply(this, arguments); delete this._dragging; }, /** * APIMethod: setOpacity * Sets the opacity for the entire layer (all images) * * Parameters: * opacity - {Float} */ setOpacity: function(opacity) { if (opacity !== this.opacity) { if (this.map != null) { this.map.events.triggerEvent("changelayer", { layer: this, property: "opacity" }); } this.opacity = opacity; } // Though this layer's opacity may not change, we're sharing a container // and need to update the opacity for the entire container. if (this.getVisibility()) { var container = this.getMapContainer(); OpenLayers.Util.modifyDOMElement( container, null, null, null, null, null, null, opacity ); } }, /** * APIMethod: destroy * Clean up this layer. */ destroy: function() { /** * We have to override this method because the event pane destroy * deletes the mapObject reference before removing this layer from * the map. */ if (this.map) { this.setGMapVisibility(false); var cache = OpenLayers.Layer.Google.cache[this.map.id]; if (cache && cache.count <= 1) { this.removeGMapElements(); } } OpenLayers.Layer.EventPane.prototype.destroy.apply(this, arguments); }, /** * Method: removeGMapElements * Remove all elements added to the dom. This should only be called if * this is the last of the Google layers for the given map. */ removeGMapElements: function() { var cache = OpenLayers.Layer.Google.cache[this.map.id]; if (cache) { // remove shared elements from dom var container = this.mapObject && this.getMapContainer(); if (container && container.parentNode) { container.parentNode.removeChild(container); } var termsOfUse = cache.termsOfUse; if (termsOfUse && termsOfUse.parentNode) { termsOfUse.parentNode.removeChild(termsOfUse); } var poweredBy = cache.poweredBy; if (poweredBy && poweredBy.parentNode) { poweredBy.parentNode.removeChild(poweredBy); } if (this.mapObject && window.google && google.maps && google.maps.event && google.maps.event.clearListeners) { google.maps.event.clearListeners(this.mapObject, 'tilesloaded'); } } }, /** * APIMethod: removeMap * On being removed from the map, also remove termsOfUse and poweredBy divs * * Parameters: * map - {} */ removeMap: function(map) { // hide layer before removing if (this.visibility && this.mapObject) { this.setGMapVisibility(false); } // check to see if last Google layer in this map var cache = OpenLayers.Layer.Google.cache[map.id]; if (cache) { if (cache.count <= 1) { this.removeGMapElements(); delete OpenLayers.Layer.Google.cache[map.id]; } else { // decrement the layer count --cache.count; } } // remove references to gmap elements delete this.termsOfUse; delete this.poweredBy; delete this.mapObject; delete this.dragObject; OpenLayers.Layer.EventPane.prototype.removeMap.apply(this, arguments); }, // // TRANSLATION: MapObject Bounds <-> OpenLayers.Bounds // /** * APIMethod: getOLBoundsFromMapObjectBounds * * Parameters: * moBounds - {Object} * * Returns: * {} An , translated from the * passed-in MapObject Bounds. * Returns null if null value is passed in. */ getOLBoundsFromMapObjectBounds: function(moBounds) { var olBounds = null; if (moBounds != null) { var sw = moBounds.getSouthWest(); var ne = moBounds.getNorthEast(); if (this.sphericalMercator) { sw = this.forwardMercator(sw.lng(), sw.lat()); ne = this.forwardMercator(ne.lng(), ne.lat()); } else { sw = new OpenLayers.LonLat(sw.lng(), sw.lat()); ne = new OpenLayers.LonLat(ne.lng(), ne.lat()); } olBounds = new OpenLayers.Bounds(sw.lon, sw.lat, ne.lon, ne.lat ); } return olBounds; }, /** * APIMethod: getWarningHTML * * Returns: * {String} String with information on why layer is broken, how to get * it working. */ getWarningHTML:function() { return OpenLayers.i18n("googleWarning"); }, /************************************ * * * MapObject Interface Controls * * * ************************************/ // Get&Set Center, Zoom /** * APIMethod: getMapObjectCenter * * Returns: * {Object} The mapObject's current center in Map Object format */ getMapObjectCenter: function() { return this.mapObject.getCenter(); }, /** * APIMethod: getMapObjectZoom * * Returns: * {Integer} The mapObject's current zoom, in Map Object format */ getMapObjectZoom: function() { return this.mapObject.getZoom(); }, /************************************ * * * MapObject Primitives * * * ************************************/ // LonLat /** * APIMethod: getLongitudeFromMapObjectLonLat * * Parameters: * moLonLat - {Object} MapObject LonLat format * * Returns: * {Float} Longitude of the given MapObject LonLat */ getLongitudeFromMapObjectLonLat: function(moLonLat) { return this.sphericalMercator ? this.forwardMercator(moLonLat.lng(), moLonLat.lat()).lon : moLonLat.lng(); }, /** * APIMethod: getLatitudeFromMapObjectLonLat * * Parameters: * moLonLat - {Object} MapObject LonLat format * * Returns: * {Float} Latitude of the given MapObject LonLat */ getLatitudeFromMapObjectLonLat: function(moLonLat) { var lat = this.sphericalMercator ? this.forwardMercator(moLonLat.lng(), moLonLat.lat()).lat : moLonLat.lat(); return lat; }, // Pixel /** * APIMethod: getXFromMapObjectPixel * * Parameters: * moPixel - {Object} MapObject Pixel format * * Returns: * {Integer} X value of the MapObject Pixel */ getXFromMapObjectPixel: function(moPixel) { return moPixel.x; }, /** * APIMethod: getYFromMapObjectPixel * * Parameters: * moPixel - {Object} MapObject Pixel format * * Returns: * {Integer} Y value of the MapObject Pixel */ getYFromMapObjectPixel: function(moPixel) { return moPixel.y; }, CLASS_NAME: "OpenLayers.Layer.Google" }); /** * Property: OpenLayers.Layer.Google.cache * {Object} Cache for elements that should only be created once per map. */ OpenLayers.Layer.Google.cache = {}; /** * Constant: OpenLayers.Layer.Google.v2 * * Mixin providing functionality specific to the Google Maps API v2. * * This API has been deprecated by Google. * Developers are encouraged to migrate to v3 of the API; support for this * is provided by */ OpenLayers.Layer.Google.v2 = { /** * Property: termsOfUse * {DOMElement} Div for Google's copyright and terms of use link */ termsOfUse: null, /** * Property: poweredBy * {DOMElement} Div for Google's powered by logo and link */ poweredBy: null, /** * Property: dragObject * {GDraggableObject} Since 2.93, Google has exposed the ability to get * the maps GDraggableObject. We can now use this for smooth panning */ dragObject: null, /** * Method: loadMapObject * Load the GMap and register appropriate event listeners. If we can't * load GMap2, then display a warning message. */ loadMapObject:function() { if (!this.type) { this.type = G_NORMAL_MAP; } var mapObject, termsOfUse, poweredBy; var cache = OpenLayers.Layer.Google.cache[this.map.id]; if (cache) { // there are already Google layers added to this map mapObject = cache.mapObject; termsOfUse = cache.termsOfUse; poweredBy = cache.poweredBy; // increment the layer count ++cache.count; } else { // this is the first Google layer for this map var container = this.map.viewPortDiv; var div = document.createElement("div"); div.id = this.map.id + "_GMap2Container"; div.style.position = "absolute"; div.style.width = "100%"; div.style.height = "100%"; container.appendChild(div); // create GMap and shuffle elements try { mapObject = new GMap2(div); // move the ToS and branding stuff up to the container div termsOfUse = div.lastChild; container.appendChild(termsOfUse); termsOfUse.style.zIndex = "1100"; termsOfUse.style.right = ""; termsOfUse.style.bottom = ""; termsOfUse.className = "olLayerGoogleCopyright"; poweredBy = div.lastChild; container.appendChild(poweredBy); poweredBy.style.zIndex = "1100"; poweredBy.style.right = ""; poweredBy.style.bottom = ""; poweredBy.className = "olLayerGooglePoweredBy gmnoprint"; } catch (e) { throw(e); } // cache elements for use by any other google layers added to // this same map OpenLayers.Layer.Google.cache[this.map.id] = { mapObject: mapObject, termsOfUse: termsOfUse, poweredBy: poweredBy, count: 1 }; } this.mapObject = mapObject; this.termsOfUse = termsOfUse; this.poweredBy = poweredBy; // ensure this layer type is one of the mapObject types if (OpenLayers.Util.indexOf(this.mapObject.getMapTypes(), this.type) === -1) { this.mapObject.addMapType(this.type); } //since v 2.93 getDragObject is now available. if(typeof mapObject.getDragObject == "function") { this.dragObject = mapObject.getDragObject(); } else { this.dragPanMapObject = null; } if(this.isBaseLayer === false) { this.setGMapVisibility(this.div.style.display !== "none"); } }, /** * APIMethod: onMapResize */ onMapResize: function() { // workaround for resizing of invisible or not yet fully loaded layers // where GMap2.checkResize() does not work. We need to load the GMap // for the old div size, then checkResize(), and then call // layer.moveTo() to trigger GMap.setCenter() (which will finish // the GMap initialization). if(this.visibility && this.mapObject.isLoaded()) { this.mapObject.checkResize(); } else { if(!this._resized) { var layer = this; var handle = GEvent.addListener(this.mapObject, "load", function() { GEvent.removeListener(handle); delete layer._resized; layer.mapObject.checkResize(); layer.moveTo(layer.map.getCenter(), layer.map.getZoom()); }); } this._resized = true; } }, /** * Method: setGMapVisibility * Display the GMap container and associated elements. * * Parameters: * visible - {Boolean} Display the GMap elements. */ setGMapVisibility: function(visible) { var cache = OpenLayers.Layer.Google.cache[this.map.id]; if (cache) { var container = this.mapObject.getContainer(); if (visible === true) { this.mapObject.setMapType(this.type); container.style.display = ""; this.termsOfUse.style.left = ""; this.termsOfUse.style.display = ""; this.poweredBy.style.display = ""; cache.displayed = this.id; } else { if (cache.displayed === this.id) { delete cache.displayed; } if (!cache.displayed) { container.style.display = "none"; this.termsOfUse.style.display = "none"; // move ToU far to the left in addition to setting display // to "none", because at the end of the GMap2 load // sequence, display: none will be unset and ToU would be // visible after loading a map with a google layer that is // initially hidden. this.termsOfUse.style.left = "-9999px"; this.poweredBy.style.display = "none"; } } } }, /** * Method: getMapContainer * * Returns: * {DOMElement} the GMap container's div */ getMapContainer: function() { return this.mapObject.getContainer(); }, // // TRANSLATION: MapObject Bounds <-> OpenLayers.Bounds // /** * APIMethod: getMapObjectBoundsFromOLBounds * * Parameters: * olBounds - {} * * Returns: * {Object} A MapObject Bounds, translated from olBounds * Returns null if null value is passed in */ getMapObjectBoundsFromOLBounds: function(olBounds) { var moBounds = null; if (olBounds != null) { var sw = this.sphericalMercator ? this.inverseMercator(olBounds.bottom, olBounds.left) : new OpenLayers.LonLat(olBounds.bottom, olBounds.left); var ne = this.sphericalMercator ? this.inverseMercator(olBounds.top, olBounds.right) : new OpenLayers.LonLat(olBounds.top, olBounds.right); moBounds = new GLatLngBounds(new GLatLng(sw.lat, sw.lon), new GLatLng(ne.lat, ne.lon)); } return moBounds; }, /************************************ * * * MapObject Interface Controls * * * ************************************/ // Get&Set Center, Zoom /** * APIMethod: setMapObjectCenter * Set the mapObject to the specified center and zoom * * Parameters: * center - {Object} MapObject LonLat format * zoom - {int} MapObject zoom format */ setMapObjectCenter: function(center, zoom) { this.mapObject.setCenter(center, zoom); }, /** * APIMethod: dragPanMapObject * * Parameters: * dX - {Integer} * dY - {Integer} */ dragPanMapObject: function(dX, dY) { this.dragObject.moveBy(new GSize(-dX, dY)); }, // LonLat - Pixel Translation /** * APIMethod: getMapObjectLonLatFromMapObjectPixel * * Parameters: * moPixel - {Object} MapObject Pixel format * * Returns: * {Object} MapObject LonLat translated from MapObject Pixel */ getMapObjectLonLatFromMapObjectPixel: function(moPixel) { return this.mapObject.fromContainerPixelToLatLng(moPixel); }, /** * APIMethod: getMapObjectPixelFromMapObjectLonLat * * Parameters: * moLonLat - {Object} MapObject LonLat format * * Returns: * {Object} MapObject Pixel transtlated from MapObject LonLat */ getMapObjectPixelFromMapObjectLonLat: function(moLonLat) { return this.mapObject.fromLatLngToContainerPixel(moLonLat); }, // Bounds /** * APIMethod: getMapObjectZoomFromMapObjectBounds * * Parameters: * moBounds - {Object} MapObject Bounds format * * Returns: * {Object} MapObject Zoom for specified MapObject Bounds */ getMapObjectZoomFromMapObjectBounds: function(moBounds) { return this.mapObject.getBoundsZoomLevel(moBounds); }, /************************************ * * * MapObject Primitives * * * ************************************/ // LonLat /** * APIMethod: getMapObjectLonLatFromLonLat * * Parameters: * lon - {Float} * lat - {Float} * * Returns: * {Object} MapObject LonLat built from lon and lat params */ getMapObjectLonLatFromLonLat: function(lon, lat) { var gLatLng; if(this.sphericalMercator) { var lonlat = this.inverseMercator(lon, lat); gLatLng = new GLatLng(lonlat.lat, lonlat.lon); } else { gLatLng = new GLatLng(lat, lon); } return gLatLng; }, // Pixel /** * APIMethod: getMapObjectPixelFromXY * * Parameters: * x - {Integer} * y - {Integer} * * Returns: * {Object} MapObject Pixel from x and y parameters */ getMapObjectPixelFromXY: function(x, y) { return new GPoint(x, y); } }; /* ====================================================================== OpenLayers/Geometry.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/BaseTypes/Class.js */ /** * Class: OpenLayers.Geometry * A Geometry is a description of a geographic object. Create an instance of * this class with the constructor. This is a base class, * typical geometry types are described by subclasses of this class. * * Note that if you use the method, you must * explicitly include the OpenLayers.Format.WKT in your build. */ OpenLayers.Geometry = OpenLayers.Class({ /** * Property: id * {String} A unique identifier for this geometry. */ id: null, /** * Property: parent * {}This is set when a Geometry is added as component * of another geometry */ parent: null, /** * Property: bounds * {} The bounds of this geometry */ bounds: null, /** * Constructor: OpenLayers.Geometry * Creates a geometry object. */ initialize: function() { this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME+ "_"); }, /** * Method: destroy * Destroy this geometry. */ destroy: function() { this.id = null; this.bounds = null; }, /** * APIMethod: clone * Create a clone of this geometry. Does not set any non-standard * properties of the cloned geometry. * * Returns: * {} An exact clone of this geometry. */ clone: function() { return new OpenLayers.Geometry(); }, /** * Method: setBounds * Set the bounds for this Geometry. * * Parameters: * bounds - {} */ setBounds: function(bounds) { if (bounds) { this.bounds = bounds.clone(); } }, /** * Method: clearBounds * Nullify this components bounds and that of its parent as well. */ clearBounds: function() { this.bounds = null; if (this.parent) { this.parent.clearBounds(); } }, /** * Method: extendBounds * Extend the existing bounds to include the new bounds. * If geometry's bounds is not yet set, then set a new Bounds. * * Parameters: * newBounds - {} */ extendBounds: function(newBounds){ var bounds = this.getBounds(); if (!bounds) { this.setBounds(newBounds); } else { this.bounds.extend(newBounds); } }, /** * APIMethod: getBounds * Get the bounds for this Geometry. If bounds is not set, it * is calculated again, this makes queries faster. * * Returns: * {} */ getBounds: function() { if (this.bounds == null) { this.calculateBounds(); } return this.bounds; }, /** * APIMethod: calculateBounds * Recalculate the bounds for the geometry. */ calculateBounds: function() { // // This should be overridden by subclasses. // }, /** * APIMethod: distanceTo * Calculate the closest distance between two geometries (on the x-y plane). * * Parameters: * geometry - {} The target geometry. * options - {Object} Optional properties for configuring the distance * calculation. * * Valid options depend on the specific geometry type. * * Returns: * {Number | Object} The distance between this geometry and the target. * If details is true, the return will be an object with distance, * x0, y0, x1, and x2 properties. The x0 and y0 properties represent * the coordinates of the closest point on this geometry. The x1 and y1 * properties represent the coordinates of the closest point on the * target geometry. */ distanceTo: function(geometry, options) { }, /** * APIMethod: getVertices * Return a list of all points in this geometry. * * Parameters: * nodes - {Boolean} For lines, only return vertices that are * endpoints. If false, for lines, only vertices that are not * endpoints will be returned. If not provided, all vertices will * be returned. * * Returns: * {Array} A list of all vertices in the geometry. */ getVertices: function(nodes) { }, /** * Method: atPoint * Note - This is only an approximation based on the bounds of the * geometry. * * Parameters: * lonlat - {|Object} OpenLayers.LonLat or an * object with a 'lon' and 'lat' properties. * toleranceLon - {float} Optional tolerance in Geometric Coords * toleranceLat - {float} Optional tolerance in Geographic Coords * * Returns: * {Boolean} Whether or not the geometry is at the specified location */ atPoint: function(lonlat, toleranceLon, toleranceLat) { var atPoint = false; var bounds = this.getBounds(); if ((bounds != null) && (lonlat != null)) { var dX = (toleranceLon != null) ? toleranceLon : 0; var dY = (toleranceLat != null) ? toleranceLat : 0; var toleranceBounds = new OpenLayers.Bounds(this.bounds.left - dX, this.bounds.bottom - dY, this.bounds.right + dX, this.bounds.top + dY); atPoint = toleranceBounds.containsLonLat(lonlat); } return atPoint; }, /** * Method: getLength * Calculate the length of this geometry. This method is defined in * subclasses. * * Returns: * {Float} The length of the collection by summing its parts */ getLength: function() { //to be overridden by geometries that actually have a length // return 0.0; }, /** * Method: getArea * Calculate the area of this geometry. This method is defined in subclasses. * * Returns: * {Float} The area of the collection by summing its parts */ getArea: function() { //to be overridden by geometries that actually have an area // return 0.0; }, /** * APIMethod: getCentroid * Calculate the centroid of this geometry. This method is defined in subclasses. * * Returns: * {} The centroid of the collection */ getCentroid: function() { return null; }, /** * Method: toString * Returns a text representation of the geometry. If the WKT format is * included in a build, this will be the Well-Known Text * representation. * * Returns: * {String} String representation of this geometry. */ toString: function() { var string; if (OpenLayers.Format && OpenLayers.Format.WKT) { string = OpenLayers.Format.WKT.prototype.write( new OpenLayers.Feature.Vector(this) ); } else { string = Object.prototype.toString.call(this); } return string; }, CLASS_NAME: "OpenLayers.Geometry" }); /** * Function: OpenLayers.Geometry.fromWKT * Generate a geometry given a Well-Known Text string. For this method to * work, you must include the OpenLayers.Format.WKT in your build * explicitly. * * Parameters: * wkt - {String} A string representing the geometry in Well-Known Text. * * Returns: * {} A geometry of the appropriate class. */ OpenLayers.Geometry.fromWKT = function(wkt) { var geom; if (OpenLayers.Format && OpenLayers.Format.WKT) { var format = OpenLayers.Geometry.fromWKT.format; if (!format) { format = new OpenLayers.Format.WKT(); OpenLayers.Geometry.fromWKT.format = format; } var result = format.read(wkt); if (result instanceof OpenLayers.Feature.Vector) { geom = result.geometry; } else if (OpenLayers.Util.isArray(result)) { var len = result.length; var components = new Array(len); for (var i=0; i= seg2.x1 || seg2.x2 >= seg1.x1. In those * obvious cases where there is no intersection, the function should * not be called. * * Parameters: * seg1 - {Object} Object representing a segment with properties x1, y1, x2, * and y2. The start point is represented by x1 and y1. The end point * is represented by x2 and y2. Start and end are ordered so that x1 < x2. * seg2 - {Object} Object representing a segment with properties x1, y1, x2, * and y2. The start point is represented by x1 and y1. The end point * is represented by x2 and y2. Start and end are ordered so that x1 < x2. * options - {Object} Optional properties for calculating the intersection. * * Valid options: * point - {Boolean} Return the intersection point. If false, the actual * intersection point will not be calculated. If true and the segments * intersect, the intersection point will be returned. If true and * the segments do not intersect, false will be returned. If true and * the segments are coincident, true will be returned. * tolerance - {Number} If a non-null value is provided, if the segments are * within the tolerance distance, this will be considered an intersection. * In addition, if the point option is true and the calculated intersection * is within the tolerance distance of an end point, the endpoint will be * returned instead of the calculated intersection. Further, if the * intersection is within the tolerance of endpoints on both segments, or * if two segment endpoints are within the tolerance distance of eachother * (but no intersection is otherwise calculated), an endpoint on the * first segment provided will be returned. * * Returns: * {Boolean | } The two segments intersect. * If the point argument is true, the return will be the intersection * point or false if none exists. If point is true and the segments * are coincident, return will be true (and the instersection is equal * to the shorter segment). */ OpenLayers.Geometry.segmentsIntersect = function(seg1, seg2, options) { var point = options && options.point; var tolerance = options && options.tolerance; var intersection = false; var x11_21 = seg1.x1 - seg2.x1; var y11_21 = seg1.y1 - seg2.y1; var x12_11 = seg1.x2 - seg1.x1; var y12_11 = seg1.y2 - seg1.y1; var y22_21 = seg2.y2 - seg2.y1; var x22_21 = seg2.x2 - seg2.x1; var d = (y22_21 * x12_11) - (x22_21 * y12_11); var n1 = (x22_21 * y11_21) - (y22_21 * x11_21); var n2 = (x12_11 * y11_21) - (y12_11 * x11_21); if(d == 0) { // parallel if(n1 == 0 && n2 == 0) { // coincident intersection = true; } } else { var along1 = n1 / d; var along2 = n2 / d; if(along1 >= 0 && along1 <= 1 && along2 >=0 && along2 <= 1) { // intersect if(!point) { intersection = true; } else { // calculate the intersection point var x = seg1.x1 + (along1 * x12_11); var y = seg1.y1 + (along1 * y12_11); intersection = new OpenLayers.Geometry.Point(x, y); } } } if(tolerance) { var dist; if(intersection) { if(point) { var segs = [seg1, seg2]; var seg, x, y; // check segment endpoints for proximity to intersection // set intersection to first endpoint within the tolerance outer: for(var i=0; i<2; ++i) { seg = segs[i]; for(var j=1; j<3; ++j) { x = seg["x" + j]; y = seg["y" + j]; dist = Math.sqrt( Math.pow(x - intersection.x, 2) + Math.pow(y - intersection.y, 2) ); if(dist < tolerance) { intersection.x = x; intersection.y = y; break outer; } } } } } else { // no calculated intersection, but segments could be within // the tolerance of one another var segs = [seg1, seg2]; var source, target, x, y, p, result; // check segment endpoints for proximity to intersection // set intersection to first endpoint within the tolerance outer: for(var i=0; i<2; ++i) { source = segs[i]; target = segs[(i+1)%2]; for(var j=1; j<3; ++j) { p = {x: source["x"+j], y: source["y"+j]}; result = OpenLayers.Geometry.distanceToSegment(p, target); if(result.distance < tolerance) { if(point) { intersection = new OpenLayers.Geometry.Point(p.x, p.y); } else { intersection = true; } break outer; } } } } } return intersection; }; /** * Function: OpenLayers.Geometry.distanceToSegment * * Parameters: * point - {Object} An object with x and y properties representing the * point coordinates. * segment - {Object} An object with x1, y1, x2, and y2 properties * representing endpoint coordinates. * * Returns: * {Object} An object with distance, along, x, and y properties. The distance * will be the shortest distance between the input point and segment. * The x and y properties represent the coordinates along the segment * where the shortest distance meets the segment. The along attribute * describes how far between the two segment points the given point is. */ OpenLayers.Geometry.distanceToSegment = function(point, segment) { var result = OpenLayers.Geometry.distanceSquaredToSegment(point, segment); result.distance = Math.sqrt(result.distance); return result; }; /** * Function: OpenLayers.Geometry.distanceSquaredToSegment * * Usually the distanceToSegment function should be used. This variant however * can be used for comparisons where the exact distance is not important. * * Parameters: * point - {Object} An object with x and y properties representing the * point coordinates. * segment - {Object} An object with x1, y1, x2, and y2 properties * representing endpoint coordinates. * * Returns: * {Object} An object with squared distance, along, x, and y properties. * The distance will be the shortest distance between the input point and * segment. The x and y properties represent the coordinates along the * segment where the shortest distance meets the segment. The along * attribute describes how far between the two segment points the given * point is. */ OpenLayers.Geometry.distanceSquaredToSegment = function(point, segment) { var x0 = point.x; var y0 = point.y; var x1 = segment.x1; var y1 = segment.y1; var x2 = segment.x2; var y2 = segment.y2; var dx = x2 - x1; var dy = y2 - y1; var along = ((dx * (x0 - x1)) + (dy * (y0 - y1))) / (Math.pow(dx, 2) + Math.pow(dy, 2)); var x, y; if(along <= 0.0) { x = x1; y = y1; } else if(along >= 1.0) { x = x2; y = y2; } else { x = x1 + along * dx; y = y1 + along * dy; } return { distance: Math.pow(x - x0, 2) + Math.pow(y - y0, 2), x: x, y: y, along: along }; }; /* ====================================================================== OpenLayers/Geometry/Collection.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Geometry.js */ /** * Class: OpenLayers.Geometry.Collection * A Collection is exactly what it sounds like: A collection of different * Geometries. These are stored in the local parameter (which * can be passed as a parameter to the constructor). * * As new geometries are added to the collection, they are NOT cloned. * When removing geometries, they need to be specified by reference (ie you * have to pass in the *exact* geometry to be removed). * * The and functions here merely iterate through * the components, summing their respective areas and lengths. * * Create a new instance with the constructor. * * Inherits from: * - */ OpenLayers.Geometry.Collection = OpenLayers.Class(OpenLayers.Geometry, { /** * APIProperty: components * {Array()} The component parts of this geometry */ components: null, /** * Property: componentTypes * {Array(String)} An array of class names representing the types of * components that the collection can include. A null value means the * component types are not restricted. */ componentTypes: null, /** * Constructor: OpenLayers.Geometry.Collection * Creates a Geometry Collection -- a list of geoms. * * Parameters: * components - {Array()} Optional array of geometries * */ initialize: function (components) { OpenLayers.Geometry.prototype.initialize.apply(this, arguments); this.components = []; if (components != null) { this.addComponents(components); } }, /** * APIMethod: destroy * Destroy this geometry. */ destroy: function () { this.components.length = 0; this.components = null; OpenLayers.Geometry.prototype.destroy.apply(this, arguments); }, /** * APIMethod: clone * Clone this geometry. * * Returns: * {} An exact clone of this collection */ clone: function() { var geometry = eval("new " + this.CLASS_NAME + "()"); for(var i=0, len=this.components.length; i)} An array of geometries to add */ addComponents: function(components){ if(!(OpenLayers.Util.isArray(components))) { components = [components]; } for(var i=0, len=components.length; i} A geometry to add * index - {int} Optional index into the array to insert the component * * Returns: * {Boolean} The component geometry was successfully added */ addComponent: function(component, index) { var added = false; if(component) { if(this.componentTypes == null || (OpenLayers.Util.indexOf(this.componentTypes, component.CLASS_NAME) > -1)) { if(index != null && (index < this.components.length)) { var components1 = this.components.slice(0, index); var components2 = this.components.slice(index, this.components.length); components1.push(component); this.components = components1.concat(components2); } else { this.components.push(component); } component.parent = this; this.clearBounds(); added = true; } } return added; }, /** * APIMethod: removeComponents * Remove components from this geometry. * * Parameters: * components - {Array()} The components to be removed * * Returns: * {Boolean} A component was removed. */ removeComponents: function(components) { var removed = false; if(!(OpenLayers.Util.isArray(components))) { components = [components]; } for(var i=components.length-1; i>=0; --i) { removed = this.removeComponent(components[i]) || removed; } return removed; }, /** * Method: removeComponent * Remove a component from this geometry. * * Parameters: * component - {} * * Returns: * {Boolean} The component was removed. */ removeComponent: function(component) { OpenLayers.Util.removeItem(this.components, component); // clearBounds() so that it gets recalculated on the next call // to this.getBounds(); this.clearBounds(); return true; }, /** * APIMethod: getLength * Calculate the length of this geometry * * Returns: * {Float} The length of the geometry */ getLength: function() { var length = 0.0; for (var i=0, len=this.components.length; i. * * Returns: * {Float} The area of the collection by summing its parts */ getArea: function() { var area = 0.0; for (var i=0, len=this.components.length; i} The spatial reference system * for the geometry coordinates. If not provided, Geographic/WGS84 is * assumed. * * Reference: * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion * Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409 * * Returns: * {float} The approximate geodesic area of the geometry in square meters. */ getGeodesicArea: function(projection) { var area = 0.0; for(var i=0, len=this.components.length; i} The centroid of the collection */ getCentroid: function(weighted) { if (!weighted) { return this.components.length && this.components[0].getCentroid(); } var len = this.components.length; if (!len) { return false; } var areas = []; var centroids = []; var areaSum = 0; var minArea = Number.MAX_VALUE; var component; for (var i=0; i 0) ? area : minArea; centroids.push(centroid); } len = areas.length; if (areaSum === 0) { // all the components in this collection have 0 area // probably a collection of points -- weight all the points the same for (var i=0; i} The spatial reference system * for the geometry coordinates. If not provided, Geographic/WGS84 is * assumed. * * Returns: * {Float} The appoximate geodesic length of the geometry in meters. */ getGeodesicLength: function(projection) { var length = 0.0; for(var i=0, len=this.components.length; i} Center point for the rotation */ rotate: function(angle, origin) { for(var i=0, len=this.components.length; i} Point of origin for resizing * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1. * * Returns: * {} - The current geometry. */ resize: function(scale, origin, ratio) { for(var i=0; i} The target geometry. * options - {Object} Optional properties for configuring the distance * calculation. * * Valid options: * details - {Boolean} Return details from the distance calculation. * Default is false. * edge - {Boolean} Calculate the distance from this geometry to the * nearest edge of the target geometry. Default is true. If true, * calling distanceTo from a geometry that is wholly contained within * the target will result in a non-zero distance. If false, whenever * geometries intersect, calling distanceTo will return 0. If false, * details cannot be returned. * * Returns: * {Number | Object} The distance between this geometry and the target. * If details is true, the return will be an object with distance, * x0, y0, x1, and y1 properties. The x0 and y0 properties represent * the coordinates of the closest point on this geometry. The x1 and y1 * properties represent the coordinates of the closest point on the * target geometry. */ distanceTo: function(geometry, options) { var edge = !(options && options.edge === false); var details = edge && options && options.details; var result, best, distance; var min = Number.POSITIVE_INFINITY; for(var i=0, len=this.components.length; i} The geometry to test. * * Returns: * {Boolean} The supplied geometry is equivalent to this geometry. */ equals: function(geometry) { var equivalent = true; if(!geometry || !geometry.CLASS_NAME || (this.CLASS_NAME != geometry.CLASS_NAME)) { equivalent = false; } else if(!(OpenLayers.Util.isArray(geometry.components)) || (geometry.components.length != this.components.length)) { equivalent = false; } else { for(var i=0, len=this.components.length; i} * dest - {} * * Returns: * {} */ transform: function(source, dest) { if (source && dest) { for (var i=0, len=this.components.length; i} Any type of geometry. * * Returns: * {Boolean} The input geometry intersects this one. */ intersects: function(geometry) { var intersect = false; for(var i=0, len=this.components.length; i */ OpenLayers.Geometry.Point = OpenLayers.Class(OpenLayers.Geometry, { /** * APIProperty: x * {float} */ x: null, /** * APIProperty: y * {float} */ y: null, /** * Constructor: OpenLayers.Geometry.Point * Construct a point geometry. * * Parameters: * x - {float} * y - {float} * */ initialize: function(x, y) { OpenLayers.Geometry.prototype.initialize.apply(this, arguments); this.x = parseFloat(x); this.y = parseFloat(y); }, /** * APIMethod: clone * * Returns: * {} An exact clone of this OpenLayers.Geometry.Point */ clone: function(obj) { if (obj == null) { obj = new OpenLayers.Geometry.Point(this.x, this.y); } // catch any randomly tagged-on properties OpenLayers.Util.applyDefaults(obj, this); return obj; }, /** * Method: calculateBounds * Create a new Bounds based on the lon/lat */ calculateBounds: function () { this.bounds = new OpenLayers.Bounds(this.x, this.y, this.x, this.y); }, /** * APIMethod: distanceTo * Calculate the closest distance between two geometries (on the x-y plane). * * Parameters: * geometry - {} The target geometry. * options - {Object} Optional properties for configuring the distance * calculation. * * Valid options: * details - {Boolean} Return details from the distance calculation. * Default is false. * edge - {Boolean} Calculate the distance from this geometry to the * nearest edge of the target geometry. Default is true. If true, * calling distanceTo from a geometry that is wholly contained within * the target will result in a non-zero distance. If false, whenever * geometries intersect, calling distanceTo will return 0. If false, * details cannot be returned. * * Returns: * {Number | Object} The distance between this geometry and the target. * If details is true, the return will be an object with distance, * x0, y0, x1, and x2 properties. The x0 and y0 properties represent * the coordinates of the closest point on this geometry. The x1 and y1 * properties represent the coordinates of the closest point on the * target geometry. */ distanceTo: function(geometry, options) { var edge = !(options && options.edge === false); var details = edge && options && options.details; var distance, x0, y0, x1, y1, result; if(geometry instanceof OpenLayers.Geometry.Point) { x0 = this.x; y0 = this.y; x1 = geometry.x; y1 = geometry.y; distance = Math.sqrt(Math.pow(x0 - x1, 2) + Math.pow(y0 - y1, 2)); result = !details ? distance : {x0: x0, y0: y0, x1: x1, y1: y1, distance: distance}; } else { result = geometry.distanceTo(this, options); if(details) { // switch coord order since this geom is target result = { x0: result.x1, y0: result.y1, x1: result.x0, y1: result.y0, distance: result.distance }; } } return result; }, /** * APIMethod: equals * Determine whether another geometry is equivalent to this one. Geometries * are considered equivalent if all components have the same coordinates. * * Parameters: * geom - {} The geometry to test. * * Returns: * {Boolean} The supplied geometry is equivalent to this geometry. */ equals: function(geom) { var equals = false; if (geom != null) { equals = ((this.x == geom.x && this.y == geom.y) || (isNaN(this.x) && isNaN(this.y) && isNaN(geom.x) && isNaN(geom.y))); } return equals; }, /** * Method: toShortString * * Returns: * {String} Shortened String representation of Point object. * (ex. "5, 42") */ toShortString: function() { return (this.x + ", " + this.y); }, /** * APIMethod: move * Moves a geometry by the given displacement along positive x and y axes. * This modifies the position of the geometry and clears the cached * bounds. * * Parameters: * x - {Float} Distance to move geometry in positive x direction. * y - {Float} Distance to move geometry in positive y direction. */ move: function(x, y) { this.x = this.x + x; this.y = this.y + y; this.clearBounds(); }, /** * APIMethod: rotate * Rotate a point around another. * * Parameters: * angle - {Float} Rotation angle in degrees (measured counterclockwise * from the positive x-axis) * origin - {} Center point for the rotation */ rotate: function(angle, origin) { angle *= Math.PI / 180; var radius = this.distanceTo(origin); var theta = angle + Math.atan2(this.y - origin.y, this.x - origin.x); this.x = origin.x + (radius * Math.cos(theta)); this.y = origin.y + (radius * Math.sin(theta)); this.clearBounds(); }, /** * APIMethod: getCentroid * * Returns: * {} The centroid of the collection */ getCentroid: function() { return new OpenLayers.Geometry.Point(this.x, this.y); }, /** * APIMethod: resize * Resize a point relative to some origin. For points, this has the effect * of scaling a vector (from the origin to the point). This method is * more useful on geometry collection subclasses. * * Parameters: * scale - {Float} Ratio of the new distance from the origin to the old * distance from the origin. A scale of 2 doubles the * distance between the point and origin. * origin - {} Point of origin for resizing * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1. * * Returns: * {} - The current geometry. */ resize: function(scale, origin, ratio) { ratio = (ratio == undefined) ? 1 : ratio; this.x = origin.x + (scale * ratio * (this.x - origin.x)); this.y = origin.y + (scale * (this.y - origin.y)); this.clearBounds(); return this; }, /** * APIMethod: intersects * Determine if the input geometry intersects this one. * * Parameters: * geometry - {} Any type of geometry. * * Returns: * {Boolean} The input geometry intersects this one. */ intersects: function(geometry) { var intersect = false; if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") { intersect = this.equals(geometry); } else { intersect = geometry.intersects(this); } return intersect; }, /** * APIMethod: transform * Translate the x,y properties of the point from source to dest. * * Parameters: * source - {} * dest - {} * * Returns: * {} */ transform: function(source, dest) { if ((source && dest)) { OpenLayers.Projection.transform( this, source, dest); this.bounds = null; } return this; }, /** * APIMethod: getVertices * Return a list of all points in this geometry. * * Parameters: * nodes - {Boolean} For lines, only return vertices that are * endpoints. If false, for lines, only vertices that are not * endpoints will be returned. If not provided, all vertices will * be returned. * * Returns: * {Array} A list of all vertices in the geometry. */ getVertices: function(nodes) { return [this]; }, CLASS_NAME: "OpenLayers.Geometry.Point" }); /* ====================================================================== OpenLayers/Geometry/MultiPoint.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Geometry/Collection.js * @requires OpenLayers/Geometry/Point.js */ /** * Class: OpenLayers.Geometry.MultiPoint * MultiPoint is a collection of Points. Create a new instance with the * constructor. * * Inherits from: * - * - */ OpenLayers.Geometry.MultiPoint = OpenLayers.Class( OpenLayers.Geometry.Collection, { /** * Property: componentTypes * {Array(String)} An array of class names representing the types of * components that the collection can include. A null value means the * component types are not restricted. */ componentTypes: ["OpenLayers.Geometry.Point"], /** * Constructor: OpenLayers.Geometry.MultiPoint * Create a new MultiPoint Geometry * * Parameters: * components - {Array()} * * Returns: * {} */ /** * APIMethod: addPoint * Wrapper for * * Parameters: * point - {} Point to be added * index - {Integer} Optional index */ addPoint: function(point, index) { this.addComponent(point, index); }, /** * APIMethod: removePoint * Wrapper for * * Parameters: * point - {} Point to be removed */ removePoint: function(point){ this.removeComponent(point); }, CLASS_NAME: "OpenLayers.Geometry.MultiPoint" }); /* ====================================================================== OpenLayers/Geometry/Curve.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Geometry/MultiPoint.js */ /** * Class: OpenLayers.Geometry.Curve * A Curve is a MultiPoint, whose points are assumed to be connected. To * this end, we provide a "getLength()" function, which iterates through * the points, summing the distances between them. * * Inherits: * - */ OpenLayers.Geometry.Curve = OpenLayers.Class(OpenLayers.Geometry.MultiPoint, { /** * Property: componentTypes * {Array(String)} An array of class names representing the types of * components that the collection can include. A null * value means the component types are not restricted. */ componentTypes: ["OpenLayers.Geometry.Point"], /** * Constructor: OpenLayers.Geometry.Curve * * Parameters: * point - {Array()} */ /** * APIMethod: getLength * * Returns: * {Float} The length of the curve */ getLength: function() { var length = 0.0; if ( this.components && (this.components.length > 1)) { for(var i=1, len=this.components.length; i} The spatial reference system * for the geometry coordinates. If not provided, Geographic/WGS84 is * assumed. * * Returns: * {Float} The appoximate geodesic length of the geometry in meters. */ getGeodesicLength: function(projection) { var geom = this; // so we can work with a clone if needed if(projection) { var gg = new OpenLayers.Projection("EPSG:4326"); if(!gg.equals(projection)) { geom = this.clone().transform(projection, gg); } } var length = 0.0; if(geom.components && (geom.components.length > 1)) { var p1, p2; for(var i=1, len=geom.components.length; i */ OpenLayers.Geometry.LineString = OpenLayers.Class(OpenLayers.Geometry.Curve, { /** * Constructor: OpenLayers.Geometry.LineString * Create a new LineString geometry * * Parameters: * points - {Array()} An array of points used to * generate the linestring * */ /** * APIMethod: removeComponent * Only allows removal of a point if there are three or more points in * the linestring. (otherwise the result would be just a single point) * * Parameters: * point - {} The point to be removed * * Returns: * {Boolean} The component was removed. */ removeComponent: function(point) { var removed = this.components && (this.components.length > 2); if (removed) { OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this, arguments); } return removed; }, /** * APIMethod: intersects * Test for instersection between two geometries. This is a cheapo * implementation of the Bently-Ottmann algorigithm. It doesn't * really keep track of a sweep line data structure. It is closer * to the brute force method, except that segments are sorted and * potential intersections are only calculated when bounding boxes * intersect. * * Parameters: * geometry - {} * * Returns: * {Boolean} The input geometry intersects this geometry. */ intersects: function(geometry) { var intersect = false; var type = geometry.CLASS_NAME; if(type == "OpenLayers.Geometry.LineString" || type == "OpenLayers.Geometry.LinearRing" || type == "OpenLayers.Geometry.Point") { var segs1 = this.getSortedSegments(); var segs2; if(type == "OpenLayers.Geometry.Point") { segs2 = [{ x1: geometry.x, y1: geometry.y, x2: geometry.x, y2: geometry.y }]; } else { segs2 = geometry.getSortedSegments(); } var seg1, seg1x1, seg1x2, seg1y1, seg1y2, seg2, seg2y1, seg2y2; // sweep right outer: for(var i=0, len=segs1.length; i seg1x2) { // seg1 still left of seg2 break; } if(seg2.x2 < seg1x1) { // seg2 still left of seg1 continue; } seg2y1 = seg2.y1; seg2y2 = seg2.y2; if(Math.min(seg2y1, seg2y2) > Math.max(seg1y1, seg1y2)) { // seg2 above seg1 continue; } if(Math.max(seg2y1, seg2y2) < Math.min(seg1y1, seg1y2)) { // seg2 below seg1 continue; } if(OpenLayers.Geometry.segmentsIntersect(seg1, seg2)) { intersect = true; break outer; } } } } else { intersect = geometry.intersects(this); } return intersect; }, /** * Method: getSortedSegments * * Returns: * {Array} An array of segment objects. Segment objects have properties * x1, y1, x2, and y2. The start point is represented by x1 and y1. * The end point is represented by x2 and y2. Start and end are * ordered so that x1 < x2. */ getSortedSegments: function() { var numSeg = this.components.length - 1; var segments = new Array(numSeg), point1, point2; for(var i=0; i 0) { // sort intersections along segment var xDir = seg.x1 < seg.x2 ? 1 : -1; var yDir = seg.y1 < seg.y2 ? 1 : -1; result = { lines: lines, points: intersections.sort(function(p1, p2) { return (xDir * p1.x - xDir * p2.x) || (yDir * p1.y - yDir * p2.y); }) }; } return result; }, /** * Method: split * Use this geometry (the source) to attempt to split a target geometry. * * Parameters: * target - {} The target geometry. * options - {Object} Properties of this object will be used to determine * how the split is conducted. * * Valid options: * mutual - {Boolean} Split the source geometry in addition to the target * geometry. Default is false. * edge - {Boolean} Allow splitting when only edges intersect. Default is * true. If false, a vertex on the source must be within the tolerance * distance of the intersection to be considered a split. * tolerance - {Number} If a non-null value is provided, intersections * within the tolerance distance of an existing vertex on the source * will be assumed to occur at the vertex. * * Returns: * {Array} A list of geometries (of this same type as the target) that * result from splitting the target with the source geometry. The * source and target geometry will remain unmodified. If no split * results, null will be returned. If mutual is true and a split * results, return will be an array of two arrays - the first will be * all geometries that result from splitting the source geometry and * the second will be all geometries that result from splitting the * target geometry. */ split: function(target, options) { var results = null; var mutual = options && options.mutual; var sourceSplit, targetSplit, sourceParts, targetParts; if(target instanceof OpenLayers.Geometry.LineString) { var verts = this.getVertices(); var vert1, vert2, seg, splits, lines, point; var points = []; sourceParts = []; for(var i=0, stop=verts.length-2; i<=stop; ++i) { vert1 = verts[i]; vert2 = verts[i+1]; seg = { x1: vert1.x, y1: vert1.y, x2: vert2.x, y2: vert2.y }; targetParts = targetParts || [target]; if(mutual) { points.push(vert1.clone()); } for(var j=0; j 0) { lines.unshift(j, 1); Array.prototype.splice.apply(targetParts, lines); j += lines.length - 2; } if(mutual) { for(var k=0, len=splits.points.length; k 0 && points.length > 0) { points.push(vert2.clone()); sourceParts.push(new OpenLayers.Geometry.LineString(points)); } } else { results = target.splitWith(this, options); } if(targetParts && targetParts.length > 1) { targetSplit = true; } else { targetParts = []; } if(sourceParts && sourceParts.length > 1) { sourceSplit = true; } else { sourceParts = []; } if(targetSplit || sourceSplit) { if(mutual) { results = [sourceParts, targetParts]; } else { results = targetParts; } } return results; }, /** * Method: splitWith * Split this geometry (the target) with the given geometry (the source). * * Parameters: * geometry - {} A geometry used to split this * geometry (the source). * options - {Object} Properties of this object will be used to determine * how the split is conducted. * * Valid options: * mutual - {Boolean} Split the source geometry in addition to the target * geometry. Default is false. * edge - {Boolean} Allow splitting when only edges intersect. Default is * true. If false, a vertex on the source must be within the tolerance * distance of the intersection to be considered a split. * tolerance - {Number} If a non-null value is provided, intersections * within the tolerance distance of an existing vertex on the source * will be assumed to occur at the vertex. * * Returns: * {Array} A list of geometries (of this same type as the target) that * result from splitting the target with the source geometry. The * source and target geometry will remain unmodified. If no split * results, null will be returned. If mutual is true and a split * results, return will be an array of two arrays - the first will be * all geometries that result from splitting the source geometry and * the second will be all geometries that result from splitting the * target geometry. */ splitWith: function(geometry, options) { return geometry.split(this, options); }, /** * APIMethod: getVertices * Return a list of all points in this geometry. * * Parameters: * nodes - {Boolean} For lines, only return vertices that are * endpoints. If false, for lines, only vertices that are not * endpoints will be returned. If not provided, all vertices will * be returned. * * Returns: * {Array} A list of all vertices in the geometry. */ getVertices: function(nodes) { var vertices; if(nodes === true) { vertices = [ this.components[0], this.components[this.components.length-1] ]; } else if (nodes === false) { vertices = this.components.slice(1, this.components.length-1); } else { vertices = this.components.slice(); } return vertices; }, /** * APIMethod: distanceTo * Calculate the closest distance between two geometries (on the x-y plane). * * Parameters: * geometry - {} The target geometry. * options - {Object} Optional properties for configuring the distance * calculation. * * Valid options: * details - {Boolean} Return details from the distance calculation. * Default is false. * edge - {Boolean} Calculate the distance from this geometry to the * nearest edge of the target geometry. Default is true. If true, * calling distanceTo from a geometry that is wholly contained within * the target will result in a non-zero distance. If false, whenever * geometries intersect, calling distanceTo will return 0. If false, * details cannot be returned. * * Returns: * {Number | Object} The distance between this geometry and the target. * If details is true, the return will be an object with distance, * x0, y0, x1, and x2 properties. The x0 and y0 properties represent * the coordinates of the closest point on this geometry. The x1 and y1 * properties represent the coordinates of the closest point on the * target geometry. */ distanceTo: function(geometry, options) { var edge = !(options && options.edge === false); var details = edge && options && options.details; var result, best = {}; var min = Number.POSITIVE_INFINITY; if(geometry instanceof OpenLayers.Geometry.Point) { var segs = this.getSortedSegments(); var x = geometry.x; var y = geometry.y; var seg; for(var i=0, len=segs.length; i x && ((y > seg.y1 && y < seg.y2) || (y < seg.y1 && y > seg.y2))) { break; } } } if(details) { best = { distance: best.distance, x0: best.x, y0: best.y, x1: x, y1: y }; } else { best = best.distance; } } else if(geometry instanceof OpenLayers.Geometry.LineString) { var segs0 = this.getSortedSegments(); var segs1 = geometry.getSortedSegments(); var seg0, seg1, intersection, x0, y0; var len1 = segs1.length; var interOptions = {point: true}; outer: for(var i=0, len=segs0.length; i maxDistance) { maxDistance = distance; indexFarthest = index; } } if (maxDistance > tolerance && indexFarthest != firstPoint) { //Add the largest point that exceeds the tolerance pointIndexsToKeep.push(indexFarthest); douglasPeuckerReduction(points, firstPoint, indexFarthest, tolerance); douglasPeuckerReduction(points, indexFarthest, lastPoint, tolerance); } }; /** * Private function calculating the perpendicular distance * TODO: check whether OpenLayers.Geometry.LineString::distanceTo() is faster or slower */ var perpendicularDistance = function(point1, point2, point){ //Area = |(1/2)(x1y2 + x2y3 + x3y1 - x2y1 - x3y2 - x1y3)| *Area of triangle //Base = v((x1-x2)²+(x1-x2)²) *Base of Triangle* //Area = .5*Base*H *Solve for height //Height = Area/.5/Base var area = Math.abs(0.5 * (point1.x * point2.y + point2.x * point.y + point.x * point1.y - point2.x * point1.y - point.x * point2.y - point1.x * point.y)); var bottom = Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2)); var height = area / bottom * 2; return height; }; var firstPoint = 0; var lastPoint = points.length - 1; var pointIndexsToKeep = []; //Add the first and last index to the keepers pointIndexsToKeep.push(firstPoint); pointIndexsToKeep.push(lastPoint); //The first and the last point cannot be the same while (points[firstPoint].equals(points[lastPoint])) { lastPoint--; //Addition: the first point not equal to first point in the LineString is kept as well pointIndexsToKeep.push(lastPoint); } douglasPeuckerReduction(points, firstPoint, lastPoint, tolerance); var returnPoints = []; pointIndexsToKeep.sort(compareNumbers); for (var index = 0; index < pointIndexsToKeep.length; index++) { returnPoints.push(points[pointIndexsToKeep[index]]); } return new OpenLayers.Geometry.LineString(returnPoints); } else { return this; } }, CLASS_NAME: "OpenLayers.Geometry.LineString" }); /* ====================================================================== OpenLayers/Geometry/LinearRing.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Geometry/LineString.js */ /** * Class: OpenLayers.Geometry.LinearRing * * A Linear Ring is a special LineString which is closed. It closes itself * automatically on every addPoint/removePoint by adding a copy of the first * point as the last point. * * Also, as it is the first in the line family to close itself, a getArea() * function is defined to calculate the enclosed area of the linearRing * * Inherits: * - */ OpenLayers.Geometry.LinearRing = OpenLayers.Class( OpenLayers.Geometry.LineString, { /** * Property: componentTypes * {Array(String)} An array of class names representing the types of * components that the collection can include. A null * value means the component types are not restricted. */ componentTypes: ["OpenLayers.Geometry.Point"], /** * Constructor: OpenLayers.Geometry.LinearRing * Linear rings are constructed with an array of points. This array * can represent a closed or open ring. If the ring is open (the last * point does not equal the first point), the constructor will close * the ring. If the ring is already closed (the last point does equal * the first point), it will be left closed. * * Parameters: * points - {Array()} points */ /** * APIMethod: addComponent * Adds a point to geometry components. If the point is to be added to * the end of the components array and it is the same as the last point * already in that array, the duplicate point is not added. This has * the effect of closing the ring if it is not already closed, and * doing the right thing if it is already closed. This behavior can * be overridden by calling the method with a non-null index as the * second argument. * * Parameters: * point - {} * index - {Integer} Index into the array to insert the component * * Returns: * {Boolean} Was the Point successfully added? */ addComponent: function(point, index) { var added = false; //remove last point var lastPoint = this.components.pop(); // given an index, add the point // without an index only add non-duplicate points if(index != null || !point.equals(lastPoint)) { added = OpenLayers.Geometry.Collection.prototype.addComponent.apply(this, arguments); } //append copy of first point var firstPoint = this.components[0]; OpenLayers.Geometry.Collection.prototype.addComponent.apply(this, [firstPoint]); return added; }, /** * APIMethod: removeComponent * Removes a point from geometry components. * * Parameters: * point - {} * * Returns: * {Boolean} The component was removed. */ removeComponent: function(point) { var removed = this.components && (this.components.length > 3); if (removed) { //remove last point this.components.pop(); //remove our point OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this, arguments); //append copy of first point var firstPoint = this.components[0]; OpenLayers.Geometry.Collection.prototype.addComponent.apply(this, [firstPoint]); } return removed; }, /** * APIMethod: move * Moves a geometry by the given displacement along positive x and y axes. * This modifies the position of the geometry and clears the cached * bounds. * * Parameters: * x - {Float} Distance to move geometry in positive x direction. * y - {Float} Distance to move geometry in positive y direction. */ move: function(x, y) { for(var i = 0, len=this.components.length; i} Center point for the rotation */ rotate: function(angle, origin) { for(var i=0, len=this.components.length; i} Point of origin for resizing * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1. * * Returns: * {} - The current geometry. */ resize: function(scale, origin, ratio) { for(var i=0, len=this.components.length; i} * dest - {} * * Returns: * {} */ transform: function(source, dest) { if (source && dest) { for (var i=0, len=this.components.length; i} The centroid of the collection */ getCentroid: function() { if (this.components) { var len = this.components.length; if (len > 0 && len <= 2) { return this.components[0].clone(); } else if (len > 2) { var sumX = 0.0; var sumY = 0.0; var x0 = this.components[0].x; var y0 = this.components[0].y; var area = -1 * this.getArea(); if (area != 0) { for (var i = 0; i < len - 1; i++) { var b = this.components[i]; var c = this.components[i+1]; sumX += (b.x + c.x - 2 * x0) * ((b.x - x0) * (c.y - y0) - (c.x - x0) * (b.y - y0)); sumY += (b.y + c.y - 2 * y0) * ((b.x - x0) * (c.y - y0) - (c.x - x0) * (b.y - y0)); } var x = x0 + sumX / (6 * area); var y = y0 + sumY / (6 * area); } else { for (var i = 0; i < len - 1; i++) { sumX += this.components[i].x; sumY += this.components[i].y; } var x = sumX / (len - 1); var y = sumY / (len - 1); } return new OpenLayers.Geometry.Point(x, y); } else { return null; } } }, /** * APIMethod: getArea * Note - The area is positive if the ring is oriented CW, otherwise * it will be negative. * * Returns: * {Float} The signed area for a ring. */ getArea: function() { var area = 0.0; if ( this.components && (this.components.length > 2)) { var sum = 0.0; for (var i=0, len=this.components.length; i} The spatial reference system * for the geometry coordinates. If not provided, Geographic/WGS84 is * assumed. * * Reference: * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion * Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409 * * Returns: * {float} The approximate signed geodesic area of the polygon in square * meters. */ getGeodesicArea: function(projection) { var ring = this; // so we can work with a clone if needed if(projection) { var gg = new OpenLayers.Projection("EPSG:4326"); if(!gg.equals(projection)) { ring = this.clone().transform(projection, gg); } } var area = 0.0; var len = ring.components && ring.components.length; if(len > 2) { var p1, p2; for(var i=0; i} * * Returns: * {Boolean | Number} The point is inside the linear ring. Returns 1 if * the point is coincident with an edge. Returns boolean otherwise. */ containsPoint: function(point) { var approx = OpenLayers.Number.limitSigDigs; var digs = 14; var px = approx(point.x, digs); var py = approx(point.y, digs); function getX(y, x1, y1, x2, y2) { return (y - y2) * ((x2 - x1) / (y2 - y1)) + x2; } var numSeg = this.components.length - 1; var start, end, x1, y1, x2, y2, cx, cy; var crosses = 0; for(var i=0; i= x1 && px <= x2) || // right or vert x1 >= x2 && (px <= x1 && px >= x2)) { // left or vert // point on edge crosses = -1; break; } } // ignore other horizontal edges continue; } cx = approx(getX(py, x1, y1, x2, y2), digs); if(cx == px) { // point on line if(y1 < y2 && (py >= y1 && py <= y2) || // upward y1 > y2 && (py <= y1 && py >= y2)) { // downward // point on edge crosses = -1; break; } } if(cx <= px) { // no crossing to the right continue; } if(x1 != x2 && (cx < Math.min(x1, x2) || cx > Math.max(x1, x2))) { // no crossing continue; } if(y1 < y2 && (py >= y1 && py < y2) || // upward y1 > y2 && (py < y1 && py >= y2)) { // downward ++crosses; } } var contained = (crosses == -1) ? // on edge 1 : // even (out) or odd (in) !!(crosses & 1); return contained; }, /** * APIMethod: intersects * Determine if the input geometry intersects this one. * * Parameters: * geometry - {} Any type of geometry. * * Returns: * {Boolean} The input geometry intersects this one. */ intersects: function(geometry) { var intersect = false; if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") { intersect = this.containsPoint(geometry); } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LineString") { intersect = geometry.intersects(this); } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") { intersect = OpenLayers.Geometry.LineString.prototype.intersects.apply( this, [geometry] ); } else { // check for component intersections for(var i=0, len=geometry.components.length; i */ OpenLayers.Layer.HTTPRequest = OpenLayers.Class(OpenLayers.Layer, { /** * Constant: URL_HASH_FACTOR * {Float} Used to hash URL param strings for multi-WMS server selection. * Set to the Golden Ratio per Knuth's recommendation. */ URL_HASH_FACTOR: (Math.sqrt(5) - 1) / 2, /** * Property: url * {Array(String) or String} This is either an array of url strings or * a single url string. */ url: null, /** * Property: params * {Object} Hashtable of key/value parameters */ params: null, /** * APIProperty: reproject * *Deprecated*. See http://docs.openlayers.org/library/spherical_mercator.html * for information on the replacement for this functionality. * {Boolean} Whether layer should reproject itself based on base layer * locations. This allows reprojection onto commercial layers. * Default is false: Most layers can't reproject, but layers * which can create non-square geographic pixels can, like WMS. * */ reproject: false, /** * Constructor: OpenLayers.Layer.HTTPRequest * * Parameters: * name - {String} * url - {Array(String) or String} * params - {Object} * options - {Object} Hashtable of extra options to tag onto the layer */ initialize: function(name, url, params, options) { OpenLayers.Layer.prototype.initialize.apply(this, [name, options]); this.url = url; if (!this.params) { this.params = OpenLayers.Util.extend({}, params); } }, /** * APIMethod: destroy */ destroy: function() { this.url = null; this.params = null; OpenLayers.Layer.prototype.destroy.apply(this, arguments); }, /** * APIMethod: clone * * Parameters: * obj - {Object} * * Returns: * {} An exact clone of this * */ clone: function (obj) { if (obj == null) { obj = new OpenLayers.Layer.HTTPRequest(this.name, this.url, this.params, this.getOptions()); } //get all additions from superclasses obj = OpenLayers.Layer.prototype.clone.apply(this, [obj]); // copy/set any non-init, non-simple values here return obj; }, /** * APIMethod: setUrl * * Parameters: * newUrl - {String} */ setUrl: function(newUrl) { this.url = newUrl; }, /** * APIMethod: mergeNewParams * * Parameters: * newParams - {Object} * * Returns: * redrawn: {Boolean} whether the layer was actually redrawn. */ mergeNewParams:function(newParams) { this.params = OpenLayers.Util.extend(this.params, newParams); var ret = this.redraw(); if(this.map != null) { this.map.events.triggerEvent("changelayer", { layer: this, property: "params" }); } return ret; }, /** * APIMethod: redraw * Redraws the layer. Returns true if the layer was redrawn, false if not. * * Parameters: * force - {Boolean} Force redraw by adding random parameter. * * Returns: * {Boolean} The layer was redrawn. */ redraw: function(force) { if (force) { return this.mergeNewParams({"_olSalt": Math.random()}); } else { return OpenLayers.Layer.prototype.redraw.apply(this, []); } }, /** * Method: selectUrl * selectUrl() implements the standard floating-point multiplicative * hash function described by Knuth, and hashes the contents of the * given param string into a float between 0 and 1. This float is then * scaled to the size of the provided urls array, and used to select * a URL. * * Parameters: * paramString - {String} * urls - {Array(String)} * * Returns: * {String} An entry from the urls array, deterministically selected based * on the paramString. */ selectUrl: function(paramString, urls) { var product = 1; for (var i=0, len=paramString.length; i constructor, or a subclass. * * TBD 3.0 - remove reference to url in above paragraph * */ OpenLayers.Tile = OpenLayers.Class({ /** * APIProperty: events * {} An events object that handles all * events on the tile. * * Register a listener for a particular event with the following syntax: * (code) * tile.events.register(type, obj, listener); * (end) * * Supported event types: * beforedraw - Triggered before the tile is drawn. Used to defer * drawing to an animation queue. To defer drawing, listeners need * to return false, which will abort drawing. The queue handler needs * to call (true) to actually draw the tile. * loadstart - Triggered when tile loading starts. * loadend - Triggered when tile loading ends. * loaderror - Triggered before the loadend event (i.e. when the tile is * still hidden) if the tile could not be loaded. * reload - Triggered when an already loading tile is reloaded. * unload - Triggered before a tile is unloaded. */ events: null, /** * APIProperty: eventListeners * {Object} If set as an option at construction, the eventListeners * object will be registered with . Object * structure must be a listeners object as shown in the example for * the events.on method. * * This options can be set in the ``tileOptions`` option from * . For example, to be notified of the * ``loadend`` event of each tiles: * (code) * new OpenLayers.Layer.OSM('osm', 'http://tile.openstreetmap.org/${z}/${x}/${y}.png', { * tileOptions: { * eventListeners: { * 'loadend': function(evt) { * // do something on loadend * } * } * } * }); * (end) */ eventListeners: null, /** * Property: id * {String} null */ id: null, /** * Property: layer * {} layer the tile is attached to */ layer: null, /** * Property: url * {String} url of the request. * * TBD 3.0 * Deprecated. The base tile class does not need an url. This should be * handled in subclasses. Does not belong here. */ url: null, /** * APIProperty: bounds * {} null */ bounds: null, /** * Property: size * {} null */ size: null, /** * Property: position * {} Top Left pixel of the tile */ position: null, /** * Property: isLoading * {Boolean} Is the tile loading? */ isLoading: false, /** TBD 3.0 -- remove 'url' from the list of parameters to the constructor. * there is no need for the base tile class to have a url. */ /** * Constructor: OpenLayers.Tile * Constructor for a new instance. * * Parameters: * layer - {} layer that the tile will go in. * position - {} * bounds - {} * url - {} * size - {} * options - {Object} */ initialize: function(layer, position, bounds, url, size, options) { this.layer = layer; this.position = position.clone(); this.setBounds(bounds); this.url = url; if (size) { this.size = size.clone(); } //give the tile a unique id based on its BBOX. this.id = OpenLayers.Util.createUniqueID("Tile_"); OpenLayers.Util.extend(this, options); this.events = new OpenLayers.Events(this); if (this.eventListeners instanceof Object) { this.events.on(this.eventListeners); } }, /** * Method: unload * Call immediately before destroying if you are listening to tile * events, so that counters are properly handled if tile is still * loading at destroy-time. Will only fire an event if the tile is * still loading. */ unload: function() { if (this.isLoading) { this.isLoading = false; this.events.triggerEvent("unload"); } }, /** * APIMethod: destroy * Nullify references to prevent circular references and memory leaks. */ destroy:function() { this.layer = null; this.bounds = null; this.size = null; this.position = null; if (this.eventListeners) { this.events.un(this.eventListeners); } this.events.destroy(); this.eventListeners = null; this.events = null; }, /** * Method: draw * Clear whatever is currently in the tile, then return whether or not * it should actually be re-drawn. This is an example implementation * that can be overridden by subclasses. The minimum thing to do here * is to call and return the result from . * * Parameters: * force - {Boolean} If true, the tile will not be cleared and no beforedraw * event will be fired. This is used for drawing tiles asynchronously * after drawing has been cancelled by returning false from a beforedraw * listener. * * Returns: * {Boolean} Whether or not the tile should actually be drawn. Returns null * if a beforedraw listener returned false. */ draw: function(force) { if (!force) { //clear tile's contents and mark as not drawn this.clear(); } var draw = this.shouldDraw(); if (draw && !force && this.events.triggerEvent("beforedraw") === false) { draw = null; } return draw; }, /** * Method: shouldDraw * Return whether or not the tile should actually be (re-)drawn. The only * case where we *wouldn't* want to draw the tile is if the tile is outside * its layer's maxExtent * * Returns: * {Boolean} Whether or not the tile should actually be drawn. */ shouldDraw: function() { var withinMaxExtent = false, maxExtent = this.layer.maxExtent; if (maxExtent) { var map = this.layer.map; var worldBounds = map.baseLayer.wrapDateLine && map.getMaxExtent(); if (this.bounds.intersectsBounds(maxExtent, {inclusive: false, worldBounds: worldBounds})) { withinMaxExtent = true; } } return withinMaxExtent || this.layer.displayOutsideMaxExtent; }, /** * Method: setBounds * Sets the bounds on this instance * * Parameters: * bounds {} */ setBounds: function(bounds) { bounds = bounds.clone(); if (this.layer.map.baseLayer.wrapDateLine) { var worldExtent = this.layer.map.getMaxExtent(), tolerance = this.layer.map.getResolution(); bounds = bounds.wrapDateLine(worldExtent, { leftTolerance: tolerance, rightTolerance: tolerance }); } this.bounds = bounds; }, /** * Method: moveTo * Reposition the tile. * * Parameters: * bounds - {} * position - {} * redraw - {Boolean} Call draw method on tile after moving. * Default is true */ moveTo: function (bounds, position, redraw) { if (redraw == null) { redraw = true; } this.setBounds(bounds); this.position = position.clone(); if (redraw) { this.draw(); } }, /** * Method: clear * Clear the tile of any bounds/position-related data so that it can * be reused in a new location. */ clear: function(draw) { // to be extended by subclasses }, CLASS_NAME: "OpenLayers.Tile" }); /* ====================================================================== OpenLayers/Tile/Image.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Tile.js * @requires OpenLayers/Animation.js * @requires OpenLayers/Util.js */ /** * Class: OpenLayers.Tile.Image * Instances of OpenLayers.Tile.Image are used to manage the image tiles * used by various layers. Create a new image tile with the * constructor. * * Inherits from: * - */ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, { /** * APIProperty: events * {} An events object that handles all * events on the tile. * * Register a listener for a particular event with the following syntax: * (code) * tile.events.register(type, obj, listener); * (end) * * Supported event types (in addition to the events): * beforeload - Triggered before an image is prepared for loading, when the * url for the image is known already. Listeners may call on * the tile instance. If they do so, that image will be used and no new * one will be created. */ /** * APIProperty: url * {String} The URL of the image being requested. No default. Filled in by * layer.getURL() function. May be modified by loadstart listeners. */ url: null, /** * Property: imgDiv * {HTMLImageElement} The image for this tile. */ imgDiv: null, /** * Property: frame * {DOMElement} The image element is appended to the frame. Any gutter on * the image will be hidden behind the frame. If no gutter is set, * this will be null. */ frame: null, /** * Property: imageReloadAttempts * {Integer} Attempts to load the image. */ imageReloadAttempts: null, /** * Property: layerAlphaHack * {Boolean} True if the png alpha hack needs to be applied on the layer's div. */ layerAlphaHack: null, /** * Property: asyncRequestId * {Integer} ID of an request to see if request is still valid. This is a * number which increments by 1 for each asynchronous request. */ asyncRequestId: null, /** * APIProperty: maxGetUrlLength * {Number} If set, requests that would result in GET urls with more * characters than the number provided will be made using form-encoded * HTTP POST. It is good practice to avoid urls that are longer than 2048 * characters. * * Caution: * Older versions of Gecko based browsers (e.g. Firefox < 3.5) and most * Opera versions do not fully support this option. On all browsers, * transition effects are not supported if POST requests are used. */ maxGetUrlLength: null, /** * Property: canvasContext * {CanvasRenderingContext2D} A canvas context associated with * the tile image. */ canvasContext: null, /** * APIProperty: crossOriginKeyword * The value of the crossorigin keyword to use when loading images. This is * only relevant when using for tiles from remote * origins and should be set to either 'anonymous' or 'use-credentials' * for servers that send Access-Control-Allow-Origin headers with their * tiles. */ crossOriginKeyword: null, /** TBD 3.0 - reorder the parameters to the init function to remove * URL. the getUrl() function on the layer gets called on * each draw(), so no need to specify it here. */ /** * Constructor: OpenLayers.Tile.Image * Constructor for a new instance. * * Parameters: * layer - {} layer that the tile will go in. * position - {} * bounds - {} * url - {} Deprecated. Remove me in 3.0. * size - {} * options - {Object} */ initialize: function(layer, position, bounds, url, size, options) { OpenLayers.Tile.prototype.initialize.apply(this, arguments); this.url = url; //deprecated remove me this.layerAlphaHack = this.layer.alpha && OpenLayers.Util.alphaHack(); if (this.maxGetUrlLength != null || this.layer.gutter || this.layerAlphaHack) { // only create frame if it's needed this.frame = document.createElement("div"); this.frame.style.position = "absolute"; this.frame.style.overflow = "hidden"; } if (this.maxGetUrlLength != null) { OpenLayers.Util.extend(this, OpenLayers.Tile.Image.IFrame); } }, /** * APIMethod: destroy * nullify references to prevent circular references and memory leaks */ destroy: function() { if (this.imgDiv) { this.clear(); this.imgDiv = null; this.frame = null; } // don't handle async requests any more this.asyncRequestId = null; OpenLayers.Tile.prototype.destroy.apply(this, arguments); }, /** * Method: draw * Check that a tile should be drawn, and draw it. * * Returns: * {Boolean} Was a tile drawn? Or null if a beforedraw listener returned * false. */ draw: function() { var shouldDraw = OpenLayers.Tile.prototype.draw.apply(this, arguments); if (shouldDraw) { // The layer's reproject option is deprecated. if (this.layer != this.layer.map.baseLayer && this.layer.reproject) { // getBoundsFromBaseLayer is defined in deprecated.js. this.bounds = this.getBoundsFromBaseLayer(this.position); } if (this.isLoading) { //if we're already loading, send 'reload' instead of 'loadstart'. this._loadEvent = "reload"; } else { this.isLoading = true; this._loadEvent = "loadstart"; } this.renderTile(); this.positionTile(); } else if (shouldDraw === false) { this.unload(); } return shouldDraw; }, /** * Method: renderTile * Internal function to actually initialize the image tile, * position it correctly, and set its url. */ renderTile: function() { if (this.layer.async) { // Asynchronous image requests call the asynchronous getURL method // on the layer to fetch an image that covers 'this.bounds'. var id = this.asyncRequestId = (this.asyncRequestId || 0) + 1; this.layer.getURLasync(this.bounds, function(url) { if (id == this.asyncRequestId) { this.url = url; this.initImage(); } }, this); } else { // synchronous image requests get the url immediately. this.url = this.layer.getURL(this.bounds); this.initImage(); } }, /** * Method: positionTile * Using the properties currenty set on the layer, position the tile correctly. * This method is used both by the async and non-async versions of the Tile.Image * code. */ positionTile: function() { var style = this.getTile().style, size = this.frame ? this.size : this.layer.getImageSize(this.bounds), ratio = 1; if (this.layer instanceof OpenLayers.Layer.Grid) { ratio = this.layer.getServerResolution() / this.layer.map.getResolution(); } style.left = this.position.x + "px"; style.top = this.position.y + "px"; style.width = Math.round(ratio * size.w) + "px"; style.height = Math.round(ratio * size.h) + "px"; }, /** * Method: clear * Remove the tile from the DOM, clear it of any image related data so that * it can be reused in a new location. */ clear: function() { OpenLayers.Tile.prototype.clear.apply(this, arguments); var img = this.imgDiv; if (img) { var tile = this.getTile(); if (tile.parentNode === this.layer.div) { this.layer.div.removeChild(tile); } this.setImgSrc(); if (this.layerAlphaHack === true) { img.style.filter = ""; } OpenLayers.Element.removeClass(img, "olImageLoadError"); } this.canvasContext = null; }, /** * Method: getImage * Returns or creates and returns the tile image. */ getImage: function() { if (!this.imgDiv) { this.imgDiv = OpenLayers.Tile.Image.IMAGE.cloneNode(false); var style = this.imgDiv.style; if (this.frame) { var left = 0, top = 0; if (this.layer.gutter) { left = this.layer.gutter / this.layer.tileSize.w * 100; top = this.layer.gutter / this.layer.tileSize.h * 100; } style.left = -left + "%"; style.top = -top + "%"; style.width = (2 * left + 100) + "%"; style.height = (2 * top + 100) + "%"; } style.visibility = "hidden"; style.opacity = 0; if (this.layer.opacity < 1) { style.filter = 'alpha(opacity=' + (this.layer.opacity * 100) + ')'; } style.position = "absolute"; if (this.layerAlphaHack) { // move the image out of sight style.paddingTop = style.height; style.height = "0"; style.width = "100%"; } if (this.frame) { this.frame.appendChild(this.imgDiv); } } return this.imgDiv; }, /** * APIMethod: setImage * Sets the image element for this tile. This method should only be called * from beforeload listeners. * * Parameters * img - {HTMLImageElement} The image to use for this tile. */ setImage: function(img) { this.imgDiv = img; }, /** * Method: initImage * Creates the content for the frame on the tile. */ initImage: function() { if (!this.url && !this.imgDiv) { // fast path out - if there is no tile url and no previous image this.isLoading = false; return; } this.events.triggerEvent('beforeload'); this.layer.div.appendChild(this.getTile()); this.events.triggerEvent(this._loadEvent); var img = this.getImage(); var src = img.getAttribute('src') || ''; if (this.url && OpenLayers.Util.isEquivalentUrl(src, this.url)) { this._loadTimeout = window.setTimeout( OpenLayers.Function.bind(this.onImageLoad, this), 0 ); } else { this.stopLoading(); if (this.crossOriginKeyword) { img.removeAttribute("crossorigin"); } OpenLayers.Event.observe(img, "load", OpenLayers.Function.bind(this.onImageLoad, this) ); OpenLayers.Event.observe(img, "error", OpenLayers.Function.bind(this.onImageError, this) ); this.imageReloadAttempts = 0; this.setImgSrc(this.url); } }, /** * Method: setImgSrc * Sets the source for the tile image * * Parameters: * url - {String} or undefined to hide the image */ setImgSrc: function(url) { var img = this.imgDiv; if (url) { img.style.visibility = 'hidden'; img.style.opacity = 0; // don't set crossOrigin if the url is a data URL if (this.crossOriginKeyword) { if (url.substr(0, 5) !== 'data:') { img.setAttribute("crossorigin", this.crossOriginKeyword); } else { img.removeAttribute("crossorigin"); } } img.src = url; } else { // Remove reference to the image, and leave it to the browser's // caching and garbage collection. this.stopLoading(); this.imgDiv = null; if (img.parentNode) { img.parentNode.removeChild(img); } } }, /** * Method: getTile * Get the tile's markup. * * Returns: * {DOMElement} The tile's markup */ getTile: function() { return this.frame ? this.frame : this.getImage(); }, /** * Method: createBackBuffer * Create a backbuffer for this tile. A backbuffer isn't exactly a clone * of the tile's markup, because we want to avoid the reloading of the * image. So we clone the frame, and steal the image from the tile. * * Returns: * {DOMElement} The markup, or undefined if the tile has no image * or if it's currently loading. */ createBackBuffer: function() { if (!this.imgDiv || this.isLoading) { return; } var backBuffer; if (this.frame) { backBuffer = this.frame.cloneNode(false); backBuffer.appendChild(this.imgDiv); } else { backBuffer = this.imgDiv; } this.imgDiv = null; return backBuffer; }, /** * Method: onImageLoad * Handler for the image onload event */ onImageLoad: function() { var img = this.imgDiv; this.stopLoading(); img.style.visibility = 'inherit'; img.style.opacity = this.layer.opacity; this.isLoading = false; this.canvasContext = null; this.events.triggerEvent("loadend"); if (this.layerAlphaHack === true) { img.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + img.src + "', sizingMethod='scale')"; } }, /** * Method: onImageError * Handler for the image onerror event */ onImageError: function() { var img = this.imgDiv; if (img.src != null) { this.imageReloadAttempts++; if (this.imageReloadAttempts <= OpenLayers.IMAGE_RELOAD_ATTEMPTS) { this.setImgSrc(this.layer.getURL(this.bounds)); } else { OpenLayers.Element.addClass(img, "olImageLoadError"); this.events.triggerEvent("loaderror"); this.onImageLoad(); } } }, /** * Method: stopLoading * Stops a loading sequence so won't be executed. */ stopLoading: function() { OpenLayers.Event.stopObservingElement(this.imgDiv); window.clearTimeout(this._loadTimeout); delete this._loadTimeout; }, /** * APIMethod: getCanvasContext * Returns a canvas context associated with the tile image (with * the image drawn on it). * Returns undefined if the browser does not support canvas, if * the tile has no image or if it's currently loading. * * The function returns a canvas context instance but the * underlying canvas is still available in the 'canvas' property: * (code) * var context = tile.getCanvasContext(); * if (context) { * var data = context.canvas.toDataURL('image/jpeg'); * } * (end) * * Returns: * {Boolean} */ getCanvasContext: function() { if (OpenLayers.CANVAS_SUPPORTED && this.imgDiv && !this.isLoading) { if (!this.canvasContext) { var canvas = document.createElement("canvas"); canvas.width = this.size.w; canvas.height = this.size.h; this.canvasContext = canvas.getContext("2d"); this.canvasContext.drawImage(this.imgDiv, 0, 0); } return this.canvasContext; } }, CLASS_NAME: "OpenLayers.Tile.Image" }); /** * Constant: OpenLayers.Tile.Image.IMAGE * {HTMLImageElement} The image for a tile. */ OpenLayers.Tile.Image.IMAGE = (function() { var img = new Image(); img.className = "olTileImage"; // avoid image gallery menu in IE6 img.galleryImg = "no"; return img; }()); /* ====================================================================== OpenLayers/Layer/Grid.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Layer/HTTPRequest.js * @requires OpenLayers/Tile/Image.js */ /** * Class: OpenLayers.Layer.Grid * Base class for layers that use a lattice of tiles. Create a new grid * layer with the constructor. * * Inherits from: * - */ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { /** * APIProperty: tileSize * {} */ tileSize: null, /** * Property: tileOriginCorner * {String} If the property is not provided, the tile origin * will be derived from the layer's . The corner of the * used is determined by this property. Acceptable values * are "tl" (top left), "tr" (top right), "bl" (bottom left), and "br" * (bottom right). Default is "bl". */ tileOriginCorner: "bl", /** * APIProperty: tileOrigin * {} Optional origin for aligning the grid of tiles. * If provided, requests for tiles at all resolutions will be aligned * with this location (no tiles shall overlap this location). If * not provided, the grid of tiles will be aligned with the layer's * . Default is ``null``. */ tileOrigin: null, /** APIProperty: tileOptions * {Object} optional configuration options for instances * created by this Layer, if supported by the tile class. */ tileOptions: null, /** * APIProperty: tileClass * {} The tile class to use for this layer. * Defaults is OpenLayers.Tile.Image. */ tileClass: OpenLayers.Tile.Image, /** * Property: grid * {Array(Array())} This is an array of rows, each row is * an array of tiles. */ grid: null, /** * APIProperty: singleTile * {Boolean} Moves the layer into single-tile mode, meaning that one tile * will be loaded. The tile's size will be determined by the 'ratio' * property. When the tile is dragged such that it does not cover the * entire viewport, it is reloaded. */ singleTile: false, /** APIProperty: ratio * {Float} Used only when in single-tile mode, this specifies the * ratio of the size of the single tile to the size of the map. * Default value is 1.5. */ ratio: 1.5, /** * APIProperty: buffer * {Integer} Used only when in gridded mode, this specifies the number of * extra rows and colums of tiles on each side which will * surround the minimum grid tiles to cover the map. * For very slow loading layers, a larger value may increase * performance somewhat when dragging, but will increase bandwidth * use significantly. */ buffer: 0, /** * APIProperty: transitionEffect * {String} The transition effect to use when the map is zoomed. * Two posible values: * * "resize" - Existing tiles are resized on zoom to provide a visual * effect of the zoom having taken place immediately. As the * new tiles become available, they are drawn on top of the * resized tiles (this is the default setting). * "map-resize" - Existing tiles are resized on zoom and placed below the * base layer. New tiles for the base layer will cover existing tiles. * This setting is recommended when having an overlay duplicated during * the transition is undesirable (e.g. street labels or big transparent * fills). * null - No transition effect. * * Using "resize" on non-opaque layers can cause undesired visual * effects. Set transitionEffect to null in this case. */ transitionEffect: "resize", /** * APIProperty: numLoadingTiles * {Integer} How many tiles are still loading? */ numLoadingTiles: 0, /** * Property: serverResolutions * {Array(Number}} This property is documented in subclasses as * an API property. */ serverResolutions: null, /** * Property: loading * {Boolean} Indicates if tiles are being loaded. */ loading: false, /** * Property: backBuffer * {DOMElement} The back buffer. */ backBuffer: null, /** * Property: gridResolution * {Number} The resolution of the current grid. Used for backbuffer and * client zoom. This property is updated every time the grid is * initialized. */ gridResolution: null, /** * Property: backBufferResolution * {Number} The resolution of the current back buffer. This property is * updated each time a back buffer is created. */ backBufferResolution: null, /** * Property: backBufferLonLat * {Object} The top-left corner of the current back buffer. Includes lon * and lat properties. This object is updated each time a back buffer * is created. */ backBufferLonLat: null, /** * Property: backBufferTimerId * {Number} The id of the back buffer timer. This timer is used to * delay the removal of the back buffer, thereby preventing * flash effects caused by tile animation. */ backBufferTimerId: null, /** * APIProperty: removeBackBufferDelay * {Number} Delay for removing the backbuffer when all tiles have finished * loading. Can be set to 0 when no css opacity transitions for the * olTileImage class are used. Default is 0 for layers, * 2500 for tiled layers. See for more information on * tile animation. */ removeBackBufferDelay: null, /** * APIProperty: className * {String} Name of the class added to the layer div. If not set in the * options passed to the constructor then className defaults to * "olLayerGridSingleTile" for single tile layers (see ), * and "olLayerGrid" for non single tile layers. * * Note: * * The displaying of tiles is not animated by default for single tile * layers - OpenLayers' default theme (style.css) includes this: * (code) * .olLayerGrid .olTileImage { * -webkit-transition: opacity 0.2s linear; * -moz-transition: opacity 0.2s linear; * -o-transition: opacity 0.2s linear; * transition: opacity 0.2s linear; * } * (end) * To animate tile displaying for any grid layer the following * CSS rule can be used: * (code) * .olTileImage { * -webkit-transition: opacity 0.2s linear; * -moz-transition: opacity 0.2s linear; * -o-transition: opacity 0.2s linear; * transition: opacity 0.2s linear; * } * (end) * In that case, to avoid flash effects, * should not be zero. */ className: null, /** * Register a listener for a particular event with the following syntax: * (code) * layer.events.register(type, obj, listener); * (end) * * Listeners will be called with a reference to an event object. The * properties of this event depends on exactly what happened. * * All event objects have at least the following properties: * object - {Object} A reference to layer.events.object. * element - {DOMElement} A reference to layer.events.element. * * Supported event types: * addtile - Triggered when a tile is added to this layer. Listeners receive * an object as first argument, which has a tile property that * references the tile that has been added. * tileloadstart - Triggered when a tile starts loading. Listeners receive * an object as first argument, which has a tile property that * references the tile that starts loading. * tileloaded - Triggered when each new tile is * loaded, as a means of progress update to listeners. * listeners can access 'numLoadingTiles' if they wish to keep * track of the loading progress. Listeners are called with an object * with a 'tile' property as first argument, making the loaded tile * available to the listener, and an 'aborted' property, which will be * true when loading was aborted and no tile data is available. * tileerror - Triggered before the tileloaded event (i.e. when the tile is * still hidden) if a tile failed to load. Listeners receive an object * as first argument, which has a tile property that references the * tile that could not be loaded. * retile - Triggered when the layer recreates its tile grid. */ /** * Property: gridLayout * {Object} Object containing properties tilelon, tilelat, startcol, * startrow */ gridLayout: null, /** * Property: rowSign * {Number} 1 for grids starting at the top, -1 for grids starting at the * bottom. This is used for several grid index and offset calculations. */ rowSign: null, /** * Property: transitionendEvents * {Array} Event names for transitionend */ transitionendEvents: [ 'transitionend', 'webkitTransitionEnd', 'otransitionend', 'oTransitionEnd' ], /** * Constructor: OpenLayers.Layer.Grid * Create a new grid layer * * Parameters: * name - {String} * url - {String} * params - {Object} * options - {Object} Hashtable of extra options to tag onto the layer */ initialize: function(name, url, params, options) { OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this, arguments); this.grid = []; this._removeBackBuffer = OpenLayers.Function.bind(this.removeBackBuffer, this); this.initProperties(); this.rowSign = this.tileOriginCorner.substr(0, 1) === "t" ? 1 : -1; }, /** * Method: initProperties * Set any properties that depend on the value of singleTile. * Currently sets removeBackBufferDelay and className */ initProperties: function() { if (this.options.removeBackBufferDelay === undefined) { this.removeBackBufferDelay = this.singleTile ? 0 : 2500; } if (this.options.className === undefined) { this.className = this.singleTile ? 'olLayerGridSingleTile' : 'olLayerGrid'; } }, /** * Method: setMap * * Parameters: * map - {} The map. */ setMap: function(map) { OpenLayers.Layer.HTTPRequest.prototype.setMap.call(this, map); OpenLayers.Element.addClass(this.div, this.className); }, /** * Method: removeMap * Called when the layer is removed from the map. * * Parameters: * map - {} The map. */ removeMap: function(map) { this.removeBackBuffer(); }, /** * APIMethod: destroy * Deconstruct the layer and clear the grid. */ destroy: function() { this.removeBackBuffer(); this.clearGrid(); this.grid = null; this.tileSize = null; OpenLayers.Layer.HTTPRequest.prototype.destroy.apply(this, arguments); }, /** * APIMethod: mergeNewParams * Refetches tiles with new params merged, keeping a backbuffer. Each * loading new tile will have a css class of '.olTileReplacing'. If a * stylesheet applies a 'display: none' style to that class, any fade-in * transition will not apply, and backbuffers for each tile will be removed * as soon as the tile is loaded. * * Parameters: * newParams - {Object} * * Returns: * redrawn: {Boolean} whether the layer was actually redrawn. */ /** * Method: clearGrid * Go through and remove all tiles from the grid, calling * destroy() on each of them to kill circular references */ clearGrid:function() { if (this.grid) { for(var iRow=0, len=this.grid.length; iRow} An exact clone of this OpenLayers.Layer.Grid */ clone: function (obj) { if (obj == null) { obj = new OpenLayers.Layer.Grid(this.name, this.url, this.params, this.getOptions()); } //get all additions from superclasses obj = OpenLayers.Layer.HTTPRequest.prototype.clone.apply(this, [obj]); // copy/set any non-init, non-simple values here if (this.tileSize != null) { obj.tileSize = this.tileSize.clone(); } // we do not want to copy reference to grid, so we make a new array obj.grid = []; obj.gridResolution = null; // same for backbuffer obj.backBuffer = null; obj.backBufferTimerId = null; obj.loading = false; obj.numLoadingTiles = 0; return obj; }, /** * Method: moveTo * This function is called whenever the map is moved. All the moving * of actual 'tiles' is done by the map, but moveTo's role is to accept * a bounds and make sure the data that that bounds requires is pre-loaded. * * Parameters: * bounds - {} * zoomChanged - {Boolean} * dragging - {Boolean} */ moveTo:function(bounds, zoomChanged, dragging) { OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this, arguments); bounds = bounds || this.map.getExtent(); if (bounds != null) { // if grid is empty or zoom has changed, we *must* re-tile var forceReTile = !this.grid.length || zoomChanged; // total bounds of the tiles var tilesBounds = this.getTilesBounds(); // the new map resolution var resolution = this.map.getResolution(); // the server-supported resolution for the new map resolution var serverResolution = this.getServerResolution(resolution); if (this.singleTile) { // We want to redraw whenever even the slightest part of the // current bounds is not contained by our tile. // (thus, we do not specify partial -- its default is false) if ( forceReTile || (!dragging && !tilesBounds.containsBounds(bounds))) { // In single tile mode with no transition effect, we insert // a non-scaled backbuffer when the layer is moved. But if // a zoom occurs right after a move, i.e. before the new // image is received, we need to remove the backbuffer, or // an ill-positioned image will be visible during the zoom // transition. if(zoomChanged && this.transitionEffect !== 'resize') { this.removeBackBuffer(); } if(!zoomChanged || this.transitionEffect === 'resize') { this.applyBackBuffer(resolution); } this.initSingleTile(bounds); } } else { // if the bounds have changed such that they are not even // *partially* contained by our tiles (e.g. when user has // programmatically panned to the other side of the earth on // zoom level 18), then moveGriddedTiles could potentially have // to run through thousands of cycles, so we want to reTile // instead (thus, partial true). forceReTile = forceReTile || !tilesBounds.intersectsBounds(bounds, { worldBounds: this.map.baseLayer.wrapDateLine && this.map.getMaxExtent() }); if(forceReTile) { if(zoomChanged && (this.transitionEffect === 'resize' || this.gridResolution === resolution)) { this.applyBackBuffer(resolution); } this.initGriddedTiles(bounds); } else { this.moveGriddedTiles(); } } } }, /** * Method: getTileData * Given a map location, retrieve a tile and the pixel offset within that * tile corresponding to the location. If there is not an existing * tile in the grid that covers the given location, null will be * returned. * * Parameters: * loc - {} map location * * Returns: * {Object} Object with the following properties: tile ({}), * i ({Number} x-pixel offset from top left), and j ({Integer} y-pixel * offset from top left). */ getTileData: function(loc) { var data = null, x = loc.lon, y = loc.lat, numRows = this.grid.length; if (this.map && numRows) { var res = this.map.getResolution(), tileWidth = this.tileSize.w, tileHeight = this.tileSize.h, bounds = this.grid[0][0].bounds, left = bounds.left, top = bounds.top; if (x < left) { // deal with multiple worlds if (this.map.baseLayer.wrapDateLine) { var worldWidth = this.map.getMaxExtent().getWidth(); var worldsAway = Math.ceil((left - x) / worldWidth); x += worldWidth * worldsAway; } } // tile distance to location (fractional number of tiles); var dtx = (x - left) / (res * tileWidth); var dty = (top - y) / (res * tileHeight); // index of tile in grid var col = Math.floor(dtx); var row = Math.floor(dty); if (row >= 0 && row < numRows) { var tile = this.grid[row][col]; if (tile) { data = { tile: tile, // pixel index within tile i: Math.floor((dtx - col) * tileWidth), j: Math.floor((dty - row) * tileHeight) }; } } } return data; }, /** * Method: destroyTile * * Parameters: * tile - {} */ destroyTile: function(tile) { this.removeTileMonitoringHooks(tile); tile.destroy(); }, /** * Method: getServerResolution * Return the closest server-supported resolution. * * Parameters: * resolution - {Number} The base resolution. If undefined the * map resolution is used. * * Returns: * {Number} The closest server resolution value. */ getServerResolution: function(resolution) { var distance = Number.POSITIVE_INFINITY; resolution = resolution || this.map.getResolution(); if(this.serverResolutions && OpenLayers.Util.indexOf(this.serverResolutions, resolution) === -1) { var i, newDistance, newResolution, serverResolution; for(i=this.serverResolutions.length-1; i>= 0; i--) { newResolution = this.serverResolutions[i]; newDistance = Math.abs(newResolution - resolution); if (newDistance > distance) { break; } distance = newDistance; serverResolution = newResolution; } resolution = serverResolution; } return resolution; }, /** * Method: getServerZoom * Return the zoom value corresponding to the best matching server * resolution, taking into account and . * * Returns: * {Number} The closest server supported zoom. This is not the map zoom * level, but an index of the server's resolutions array. */ getServerZoom: function() { var resolution = this.getServerResolution(); return this.serverResolutions ? OpenLayers.Util.indexOf(this.serverResolutions, resolution) : this.map.getZoomForResolution(resolution) + (this.zoomOffset || 0); }, /** * Method: applyBackBuffer * Create, insert, scale and position a back buffer for the layer. * * Parameters: * resolution - {Number} The resolution to transition to. */ applyBackBuffer: function(resolution) { if(this.backBufferTimerId !== null) { this.removeBackBuffer(); } var backBuffer = this.backBuffer; if(!backBuffer) { backBuffer = this.createBackBuffer(); if(!backBuffer) { return; } if (resolution === this.gridResolution) { this.div.insertBefore(backBuffer, this.div.firstChild); } else { this.map.baseLayer.div.parentNode.insertBefore(backBuffer, this.map.baseLayer.div); } this.backBuffer = backBuffer; // set some information in the instance for subsequent // calls to applyBackBuffer where the same back buffer // is reused var topLeftTileBounds = this.grid[0][0].bounds; this.backBufferLonLat = { lon: topLeftTileBounds.left, lat: topLeftTileBounds.top }; this.backBufferResolution = this.gridResolution; } var ratio = this.backBufferResolution / resolution; // scale the tiles inside the back buffer var tiles = backBuffer.childNodes, tile; for (var i=tiles.length-1; i>=0; --i) { tile = tiles[i]; tile.style.top = ((ratio * tile._i * tile._h) | 0) + 'px'; tile.style.left = ((ratio * tile._j * tile._w) | 0) + 'px'; tile.style.width = Math.round(ratio * tile._w) + 'px'; tile.style.height = Math.round(ratio * tile._h) + 'px'; } // and position it (based on the grid's top-left corner) var position = this.getViewPortPxFromLonLat( this.backBufferLonLat, resolution); var leftOffset = this.map.layerContainerOriginPx.x; var topOffset = this.map.layerContainerOriginPx.y; backBuffer.style.left = Math.round(position.x - leftOffset) + 'px'; backBuffer.style.top = Math.round(position.y - topOffset) + 'px'; }, /** * Method: createBackBuffer * Create a back buffer. * * Returns: * {DOMElement} The DOM element for the back buffer, undefined if the * grid isn't initialized yet. */ createBackBuffer: function() { var backBuffer; if(this.grid.length > 0) { backBuffer = document.createElement('div'); backBuffer.id = this.div.id + '_bb'; backBuffer.className = 'olBackBuffer'; backBuffer.style.position = 'absolute'; var map = this.map; backBuffer.style.zIndex = this.transitionEffect === 'resize' ? this.getZIndex() - 1 : // 'map-resize': map.Z_INDEX_BASE.BaseLayer - (map.getNumLayers() - map.getLayerIndex(this)); for(var i=0, lenI=this.grid.length; i=0; --i) { OpenLayers.Event.stopObserving(this._transitionElement, this.transitionendEvents[i], this._removeBackBuffer); } delete this._transitionElement; } if(this.backBuffer) { if (this.backBuffer.parentNode) { this.backBuffer.parentNode.removeChild(this.backBuffer); } this.backBuffer = null; this.backBufferResolution = null; if(this.backBufferTimerId !== null) { window.clearTimeout(this.backBufferTimerId); this.backBufferTimerId = null; } } }, /** * Method: moveByPx * Move the layer based on pixel vector. * * Parameters: * dx - {Number} * dy - {Number} */ moveByPx: function(dx, dy) { if (!this.singleTile) { this.moveGriddedTiles(); } }, /** * APIMethod: setTileSize * Check if we are in singleTile mode and if so, set the size as a ratio * of the map size (as specified by the layer's 'ratio' property). * * Parameters: * size - {} */ setTileSize: function(size) { if (this.singleTile) { size = this.map.getSize(); size.h = parseInt(size.h * this.ratio, 10); size.w = parseInt(size.w * this.ratio, 10); } OpenLayers.Layer.HTTPRequest.prototype.setTileSize.apply(this, [size]); }, /** * APIMethod: getTilesBounds * Return the bounds of the tile grid. * * Returns: * {} A Bounds object representing the bounds of all the * currently loaded tiles (including those partially or not at all seen * onscreen). */ getTilesBounds: function() { var bounds = null; var length = this.grid.length; if (length) { var bottomLeftTileBounds = this.grid[length - 1][0].bounds, width = this.grid[0].length * bottomLeftTileBounds.getWidth(), height = this.grid.length * bottomLeftTileBounds.getHeight(); bounds = new OpenLayers.Bounds(bottomLeftTileBounds.left, bottomLeftTileBounds.bottom, bottomLeftTileBounds.left + width, bottomLeftTileBounds.bottom + height); } return bounds; }, /** * Method: initSingleTile * * Parameters: * bounds - {} */ initSingleTile: function(bounds) { this.events.triggerEvent("retile"); //determine new tile bounds var center = bounds.getCenterLonLat(); var tileWidth = bounds.getWidth() * this.ratio; var tileHeight = bounds.getHeight() * this.ratio; var tileBounds = new OpenLayers.Bounds(center.lon - (tileWidth/2), center.lat - (tileHeight/2), center.lon + (tileWidth/2), center.lat + (tileHeight/2)); var px = this.map.getLayerPxFromLonLat({ lon: tileBounds.left, lat: tileBounds.top }); if (!this.grid.length) { this.grid[0] = []; } var tile = this.grid[0][0]; if (!tile) { tile = this.addTile(tileBounds, px); this.addTileMonitoringHooks(tile); tile.draw(); this.grid[0][0] = tile; } else { tile.moveTo(tileBounds, px); } //remove all but our single tile this.removeExcessTiles(1,1); // store the resolution of the grid this.gridResolution = this.getServerResolution(); }, /** * Method: calculateGridLayout * Generate parameters for the grid layout. * * Parameters: * bounds - {|Object} OpenLayers.Bounds or an * object with a 'left' and 'top' properties. * origin - {|Object} OpenLayers.LonLat or an * object with a 'lon' and 'lat' properties. * resolution - {Number} * * Returns: * {Object} Object containing properties tilelon, tilelat, startcol, * startrow */ calculateGridLayout: function(bounds, origin, resolution) { var tilelon = resolution * this.tileSize.w; var tilelat = resolution * this.tileSize.h; var offsetlon = bounds.left - origin.lon; var tilecol = Math.floor(offsetlon/tilelon) - this.buffer; var rowSign = this.rowSign; var offsetlat = rowSign * (origin.lat - bounds.top + tilelat); var tilerow = Math[~rowSign ? 'floor' : 'ceil'](offsetlat/tilelat) - this.buffer * rowSign; return { tilelon: tilelon, tilelat: tilelat, startcol: tilecol, startrow: tilerow }; }, /** * Method: getTileOrigin * Determine the origin for aligning the grid of tiles. If a * property is supplied, that will be returned. Otherwise, the origin * will be derived from the layer's property. In this case, * the tile origin will be the corner of the given by the * property. * * Returns: * {} The tile origin. */ getTileOrigin: function() { var origin = this.tileOrigin; if (!origin) { var extent = this.getMaxExtent(); var edges = ({ "tl": ["left", "top"], "tr": ["right", "top"], "bl": ["left", "bottom"], "br": ["right", "bottom"] })[this.tileOriginCorner]; origin = new OpenLayers.LonLat(extent[edges[0]], extent[edges[1]]); } return origin; }, /** * Method: getTileBoundsForGridIndex * * Parameters: * row - {Number} The row of the grid * col - {Number} The column of the grid * * Returns: * {} The bounds for the tile at (row, col) */ getTileBoundsForGridIndex: function(row, col) { var origin = this.getTileOrigin(); var tileLayout = this.gridLayout; var tilelon = tileLayout.tilelon; var tilelat = tileLayout.tilelat; var startcol = tileLayout.startcol; var startrow = tileLayout.startrow; var rowSign = this.rowSign; return new OpenLayers.Bounds( origin.lon + (startcol + col) * tilelon, origin.lat - (startrow + row * rowSign) * tilelat * rowSign, origin.lon + (startcol + col + 1) * tilelon, origin.lat - (startrow + (row - 1) * rowSign) * tilelat * rowSign ); }, /** * Method: initGriddedTiles * * Parameters: * bounds - {} */ initGriddedTiles:function(bounds) { this.events.triggerEvent("retile"); // work out mininum number of rows and columns; this is the number of // tiles required to cover the viewport plus at least one for panning var viewSize = this.map.getSize(); var origin = this.getTileOrigin(); var resolution = this.map.getResolution(), serverResolution = this.getServerResolution(), ratio = resolution / serverResolution, tileSize = { w: this.tileSize.w / ratio, h: this.tileSize.h / ratio }; var minRows = Math.ceil(viewSize.h/tileSize.h) + 2 * this.buffer + 1; var minCols = Math.ceil(viewSize.w/tileSize.w) + 2 * this.buffer + 1; var tileLayout = this.calculateGridLayout(bounds, origin, serverResolution); this.gridLayout = tileLayout; var tilelon = tileLayout.tilelon; var tilelat = tileLayout.tilelat; var layerContainerDivLeft = this.map.layerContainerOriginPx.x; var layerContainerDivTop = this.map.layerContainerOriginPx.y; var tileBounds = this.getTileBoundsForGridIndex(0, 0); var startPx = this.map.getViewPortPxFromLonLat( new OpenLayers.LonLat(tileBounds.left, tileBounds.top) ); startPx.x = Math.round(startPx.x) - layerContainerDivLeft; startPx.y = Math.round(startPx.y) - layerContainerDivTop; var tileData = [], center = this.map.getCenter(); var rowidx = 0; do { var row = this.grid[rowidx]; if (!row) { row = []; this.grid.push(row); } var colidx = 0; do { tileBounds = this.getTileBoundsForGridIndex(rowidx, colidx); var px = startPx.clone(); px.x = px.x + colidx * Math.round(tileSize.w); px.y = px.y + rowidx * Math.round(tileSize.h); var tile = row[colidx]; if (!tile) { tile = this.addTile(tileBounds, px); this.addTileMonitoringHooks(tile); row.push(tile); } else { tile.moveTo(tileBounds, px, false); } var tileCenter = tileBounds.getCenterLonLat(); tileData.push({ tile: tile, distance: Math.pow(tileCenter.lon - center.lon, 2) + Math.pow(tileCenter.lat - center.lat, 2) }); colidx += 1; } while ((tileBounds.right <= bounds.right + tilelon * this.buffer) || colidx < minCols); rowidx += 1; } while((tileBounds.bottom >= bounds.bottom - tilelat * this.buffer) || rowidx < minRows); //shave off exceess rows and colums this.removeExcessTiles(rowidx, colidx); var resolution = this.getServerResolution(); // store the resolution of the grid this.gridResolution = resolution; //now actually draw the tiles tileData.sort(function(a, b) { return a.distance - b.distance; }); for (var i=0, ii=tileData.length; i} */ getMaxExtent: function() { return this.maxExtent; }, /** * APIMethod: addTile * Create a tile, initialize it, and add it to the layer div. * * Parameters * bounds - {} * position - {} * * Returns: * {} The added OpenLayers.Tile */ addTile: function(bounds, position) { var tile = new this.tileClass( this, position, bounds, null, this.tileSize, this.tileOptions ); this.events.triggerEvent("addtile", {tile: tile}); return tile; }, /** * Method: addTileMonitoringHooks * This function takes a tile as input and adds the appropriate hooks to * the tile so that the layer can keep track of the loading tiles. * * Parameters: * tile - {} */ addTileMonitoringHooks: function(tile) { var replacingCls = 'olTileReplacing'; tile.onLoadStart = function() { //if that was first tile then trigger a 'loadstart' on the layer if (this.loading === false) { this.loading = true; this.events.triggerEvent("loadstart"); } this.events.triggerEvent("tileloadstart", {tile: tile}); this.numLoadingTiles++; if (!this.singleTile && this.backBuffer && this.gridResolution === this.backBufferResolution) { OpenLayers.Element.addClass(tile.getTile(), replacingCls); } }; tile.onLoadEnd = function(evt) { this.numLoadingTiles--; var aborted = evt.type === 'unload'; this.events.triggerEvent("tileloaded", { tile: tile, aborted: aborted }); if (!this.singleTile && !aborted && this.backBuffer && this.gridResolution === this.backBufferResolution) { var tileDiv = tile.getTile(); if (OpenLayers.Element.getStyle(tileDiv, 'display') === 'none') { var bufferTile = document.getElementById(tile.id + '_bb'); if (bufferTile) { bufferTile.parentNode.removeChild(bufferTile); } } OpenLayers.Element.removeClass(tileDiv, replacingCls); } //if that was the last tile, then trigger a 'loadend' on the layer if (this.numLoadingTiles === 0) { if (this.backBuffer) { if (this.backBuffer.childNodes.length === 0) { // no tiles transitioning, remove immediately this.removeBackBuffer(); } else { // wait until transition has ended or delay has passed this._transitionElement = aborted ? this.div.lastChild : tile.imgDiv; var transitionendEvents = this.transitionendEvents; for (var i=transitionendEvents.length-1; i>=0; --i) { OpenLayers.Event.observe(this._transitionElement, transitionendEvents[i], this._removeBackBuffer); } // the removal of the back buffer is delayed to prevent // flash effects due to the animation of tile displaying this.backBufferTimerId = window.setTimeout( this._removeBackBuffer, this.removeBackBufferDelay ); } } this.loading = false; this.events.triggerEvent("loadend"); } }; tile.onLoadError = function() { this.events.triggerEvent("tileerror", {tile: tile}); }; tile.events.on({ "loadstart": tile.onLoadStart, "loadend": tile.onLoadEnd, "unload": tile.onLoadEnd, "loaderror": tile.onLoadError, scope: this }); }, /** * Method: removeTileMonitoringHooks * This function takes a tile as input and removes the tile hooks * that were added in addTileMonitoringHooks() * * Parameters: * tile - {} */ removeTileMonitoringHooks: function(tile) { tile.unload(); tile.events.un({ "loadstart": tile.onLoadStart, "loadend": tile.onLoadEnd, "unload": tile.onLoadEnd, "loaderror": tile.onLoadError, scope: this }); }, /** * Method: moveGriddedTiles */ moveGriddedTiles: function() { var buffer = this.buffer + 1; while(true) { var tlTile = this.grid[0][0]; var tlViewPort = { x: tlTile.position.x + this.map.layerContainerOriginPx.x, y: tlTile.position.y + this.map.layerContainerOriginPx.y }; var ratio = this.getServerResolution() / this.map.getResolution(); var tileSize = { w: Math.round(this.tileSize.w * ratio), h: Math.round(this.tileSize.h * ratio) }; if (tlViewPort.x > -tileSize.w * (buffer - 1)) { this.shiftColumn(true, tileSize); } else if (tlViewPort.x < -tileSize.w * buffer) { this.shiftColumn(false, tileSize); } else if (tlViewPort.y > -tileSize.h * (buffer - 1)) { this.shiftRow(true, tileSize); } else if (tlViewPort.y < -tileSize.h * buffer) { this.shiftRow(false, tileSize); } else { break; } } }, /** * Method: shiftRow * Shifty grid work * * Parameters: * prepend - {Boolean} if true, prepend to beginning. * if false, then append to end * tileSize - {Object} rendered tile size; object with w and h properties */ shiftRow: function(prepend, tileSize) { var grid = this.grid; var rowIndex = prepend ? 0 : (grid.length - 1); var sign = prepend ? -1 : 1; var rowSign = this.rowSign; var tileLayout = this.gridLayout; tileLayout.startrow += sign * rowSign; var modelRow = grid[rowIndex]; var row = grid[prepend ? 'pop' : 'shift'](); for (var i=0, len=row.length; i rows) { var row = this.grid.pop(); for (i=0, l=row.length; i columns) { var row = this.grid[i]; var tile = row.pop(); this.destroyTile(tile); } } }, /** * Method: onMapResize * For singleTile layers, this will set a new tile size according to the * dimensions of the map pane. */ onMapResize: function() { if (this.singleTile) { this.clearGrid(); this.setTileSize(); } }, /** * APIMethod: getTileBounds * Returns The tile bounds for a layer given a pixel location. * * Parameters: * viewPortPx - {} The location in the viewport. * * Returns: * {} Bounds of the tile at the given pixel location. */ getTileBounds: function(viewPortPx) { var maxExtent = this.maxExtent; var resolution = this.getResolution(); var tileMapWidth = resolution * this.tileSize.w; var tileMapHeight = resolution * this.tileSize.h; var mapPoint = this.getLonLatFromViewPortPx(viewPortPx); var tileLeft = maxExtent.left + (tileMapWidth * Math.floor((mapPoint.lon - maxExtent.left) / tileMapWidth)); var tileBottom = maxExtent.bottom + (tileMapHeight * Math.floor((mapPoint.lat - maxExtent.bottom) / tileMapHeight)); return new OpenLayers.Bounds(tileLeft, tileBottom, tileLeft + tileMapWidth, tileBottom + tileMapHeight); }, CLASS_NAME: "OpenLayers.Layer.Grid" }); /* ====================================================================== OpenLayers/Layer/XYZ.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Layer/Grid.js */ /** * Class: OpenLayers.Layer.XYZ * The XYZ class is designed to make it easier for people who have tiles * arranged by a standard XYZ grid. * * Inherits from: * - */ OpenLayers.Layer.XYZ = OpenLayers.Class(OpenLayers.Layer.Grid, { /** * APIProperty: isBaseLayer * Default is true, as this is designed to be a base tile source. */ isBaseLayer: true, /** * APIProperty: sphericalMercator * Whether the tile extents should be set to the defaults for * spherical mercator. Useful for things like OpenStreetMap. * Default is false, except for the OSM subclass. */ sphericalMercator: false, /** * APIProperty: zoomOffset * {Number} If your cache has more zoom levels than you want to provide * access to with this layer, supply a zoomOffset. This zoom offset * is added to the current map zoom level to determine the level * for a requested tile. For example, if you supply a zoomOffset * of 3, when the map is at the zoom 0, tiles will be requested from * level 3 of your cache. Default is 0 (assumes cache level and map * zoom are equivalent). Using is an alternative to * setting if you only want to expose a subset * of the server resolutions. */ zoomOffset: 0, /** * APIProperty: serverResolutions * {Array} A list of all resolutions available on the server. Only set this * property if the map resolutions differ from the server. This * property serves two purposes. (a) can include * resolutions that the server supports and that you don't want to * provide with this layer; you can also look at , which is * an alternative to for that specific purpose. * (b) The map can work with resolutions that aren't supported by * the server, i.e. that aren't in . When the * map is displayed in such a resolution data for the closest * server-supported resolution is loaded and the layer div is * stretched as necessary. */ serverResolutions: null, /** * Constructor: OpenLayers.Layer.XYZ * * Parameters: * name - {String} * url - {String} * options - {Object} Hashtable of extra options to tag onto the layer */ initialize: function(name, url, options) { if (options && options.sphericalMercator || this.sphericalMercator) { options = OpenLayers.Util.extend({ projection: "EPSG:900913", numZoomLevels: 19 }, options); } OpenLayers.Layer.Grid.prototype.initialize.apply(this, [ name || this.name, url || this.url, {}, options ]); }, /** * APIMethod: clone * Create a clone of this layer * * Parameters: * obj - {Object} Is this ever used? * * Returns: * {} An exact clone of this OpenLayers.Layer.XYZ */ clone: function (obj) { if (obj == null) { obj = new OpenLayers.Layer.XYZ(this.name, this.url, this.getOptions()); } //get all additions from superclasses obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]); return obj; }, /** * Method: getURL * * Parameters: * bounds - {} * * Returns: * {String} A string with the layer's url and parameters and also the * passed-in bounds and appropriate tile size specified as * parameters */ getURL: function (bounds) { var xyz = this.getXYZ(bounds); var url = this.url; if (OpenLayers.Util.isArray(url)) { var s = '' + xyz.x + xyz.y + xyz.z; url = this.selectUrl(s, url); } return OpenLayers.String.format(url, xyz); }, /** * Method: getXYZ * Calculates x, y and z for the given bounds. * * Parameters: * bounds - {} * * Returns: * {Object} - an object with x, y and z properties. */ getXYZ: function(bounds) { var res = this.getServerResolution(); var x = Math.round((bounds.left - this.maxExtent.left) / (res * this.tileSize.w)); var y = Math.round((this.maxExtent.top - bounds.top) / (res * this.tileSize.h)); var z = this.getServerZoom(); if (this.wrapDateLine) { var limit = Math.pow(2, z); x = ((x % limit) + limit) % limit; } return {'x': x, 'y': y, 'z': z}; }, /* APIMethod: setMap * When the layer is added to a map, then we can fetch our origin * (if we don't have one.) * * Parameters: * map - {} */ setMap: function(map) { OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments); if (!this.tileOrigin) { this.tileOrigin = new OpenLayers.LonLat(this.maxExtent.left, this.maxExtent.bottom); } }, CLASS_NAME: "OpenLayers.Layer.XYZ" }); /* ====================================================================== OpenLayers/Layer/OSM.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Layer/XYZ.js */ /** * Class: OpenLayers.Layer.OSM * This layer allows accessing OpenStreetMap tiles. By default the OpenStreetMap * hosted tile.openstreetmap.org Mapnik tileset is used. If you wish to use * a different layer instead, you need to provide a different * URL to the constructor. Here's an example for using OpenCycleMap: * * (code) * new OpenLayers.Layer.OSM("OpenCycleMap", * ["http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png", * "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png", * "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png"]); * (end) * * Inherits from: * - */ OpenLayers.Layer.OSM = OpenLayers.Class(OpenLayers.Layer.XYZ, { /** * APIProperty: name * {String} The layer name. Defaults to "OpenStreetMap" if the first * argument to the constructor is null or undefined. */ name: "OpenStreetMap", /** * APIProperty: url * {String} The tileset URL scheme. Defaults to * : http://[a|b|c].tile.openstreetmap.org/${z}/${x}/${y}.png * (the official OSM tileset) if the second argument to the constructor * is null or undefined. To use another tileset you can have something * like this: * (code) * new OpenLayers.Layer.OSM("OpenCycleMap", * ["http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png", * "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png", * "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png"]); * (end) */ url: [ 'http://a.tile.openstreetmap.org/${z}/${x}/${y}.png', 'http://b.tile.openstreetmap.org/${z}/${x}/${y}.png', 'http://c.tile.openstreetmap.org/${z}/${x}/${y}.png' ], /** * Property: attribution * {String} The layer attribution. */ attribution: "© OpenStreetMap contributors", /** * Property: sphericalMercator * {Boolean} */ sphericalMercator: true, /** * Property: wrapDateLine * {Boolean} */ wrapDateLine: true, /** APIProperty: tileOptions * {Object} optional configuration options for instances * created by this Layer. Default is * * (code) * {crossOriginKeyword: 'anonymous'} * (end) * * When using OSM tilesets other than the default ones, it may be * necessary to set this to * * (code) * {crossOriginKeyword: null} * (end) * * if the server does not send Access-Control-Allow-Origin headers. */ tileOptions: null, /** * Constructor: OpenLayers.Layer.OSM * * Parameters: * name - {String} The layer name. * url - {String} The tileset URL scheme. * options - {Object} Configuration options for the layer. Any inherited * layer option can be set in this object (e.g. * ). */ initialize: function(name, url, options) { OpenLayers.Layer.XYZ.prototype.initialize.apply(this, arguments); this.tileOptions = OpenLayers.Util.extend({ crossOriginKeyword: 'anonymous' }, this.options && this.options.tileOptions); }, /** * Method: clone */ clone: function(obj) { if (obj == null) { obj = new OpenLayers.Layer.OSM( this.name, this.url, this.getOptions()); } obj = OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]); return obj; }, CLASS_NAME: "OpenLayers.Layer.OSM" }); /* ====================================================================== OpenLayers/Layer/Bing.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Layer/XYZ.js */ /** * Class: OpenLayers.Layer.Bing * Bing layer using direct tile access as provided by Bing Maps REST Services. * See http://msdn.microsoft.com/en-us/library/ff701713.aspx for more * information. Note: Terms of Service compliant use requires the map to be * configured with an control and the * attribution placed on or near the map. * * Inherits from: * - */ OpenLayers.Layer.Bing = OpenLayers.Class(OpenLayers.Layer.XYZ, { /** * Property: key * {String} API key for Bing maps, get your own key * at http://bingmapsportal.com/ . */ key: null, /** * Property: serverResolutions * {Array} the resolutions provided by the Bing servers. */ serverResolutions: [ 156543.03390625, 78271.516953125, 39135.7584765625, 19567.87923828125, 9783.939619140625, 4891.9698095703125, 2445.9849047851562, 1222.9924523925781, 611.4962261962891, 305.74811309814453, 152.87405654907226, 76.43702827453613, 38.218514137268066, 19.109257068634033, 9.554628534317017, 4.777314267158508, 2.388657133579254, 1.194328566789627, 0.5971642833948135, 0.29858214169740677, 0.14929107084870338, 0.07464553542435169 ], /** * Property: attributionTemplate * {String} */ attributionTemplate: '' + '${copyrights}' + '' + 'Terms of Use', /** * Property: metadata * {Object} Metadata for this layer, as returned by the callback script */ metadata: null, /** * Property: protocolRegex * {RegExp} Regular expression to match and replace http: in bing urls */ protocolRegex: /^http:/i, /** * APIProperty: type * {String} The layer identifier. Any non-birdseye imageryType * from http://msdn.microsoft.com/en-us/library/ff701716.aspx can be * used. Default is "Road". */ type: "Road", /** * APIProperty: culture * {String} The culture identifier. See http://msdn.microsoft.com/en-us/library/ff701709.aspx * for the definition and the possible values. Default is "en-US". */ culture: "en-US", /** * APIProperty: metadataParams * {Object} Optional url parameters for the Get Imagery Metadata request * as described here: http://msdn.microsoft.com/en-us/library/ff701716.aspx */ metadataParams: null, /** APIProperty: tileOptions * {Object} optional configuration options for instances * created by this Layer. Default is * * (code) * {crossOriginKeyword: 'anonymous'} * (end) */ tileOptions: null, /** APIProperty: protocol * {String} Protocol to use to fetch Imagery Metadata, tiles and bing logo * Can be 'http:' 'https:' or '' * * Warning: tiles may not be available under both HTTP and HTTPS protocols. * Microsoft approved use of both HTTP and HTTPS urls for tiles. However * this is undocumented and the Imagery Metadata API always returns HTTP * urls. * * Default is '', unless when executed from a file:/// uri, in which case * it is 'http:'. */ protocol: ~window.location.href.indexOf('http') ? '' : 'http:', /** * Constructor: OpenLayers.Layer.Bing * Create a new Bing layer. * * Example: * (code) * var road = new OpenLayers.Layer.Bing({ * name: "My Bing Aerial Layer", * type: "Aerial", * key: "my-api-key-here", * }); * (end) * * Parameters: * options - {Object} Configuration properties for the layer. * * Required configuration properties: * key - {String} Bing Maps API key for your application. Get one at * http://bingmapsportal.com/. * type - {String} The layer identifier. Any non-birdseye imageryType * from http://msdn.microsoft.com/en-us/library/ff701716.aspx can be * used. * * Any other documented layer properties can be provided in the config object. */ initialize: function(options) { options = OpenLayers.Util.applyDefaults({ sphericalMercator: true }, options); var name = options.name || "Bing " + (options.type || this.type); var newArgs = [name, null, options]; OpenLayers.Layer.XYZ.prototype.initialize.apply(this, newArgs); this.tileOptions = OpenLayers.Util.extend({ crossOriginKeyword: 'anonymous' }, this.options.tileOptions); this.loadMetadata(); }, /** * Method: loadMetadata */ loadMetadata: function() { this._callbackId = "_callback_" + this.id.replace(/\./g, "_"); // link the processMetadata method to the global scope and bind it // to this instance window[this._callbackId] = OpenLayers.Function.bind( OpenLayers.Layer.Bing.processMetadata, this ); var params = OpenLayers.Util.applyDefaults({ key: this.key, jsonp: this._callbackId, include: "ImageryProviders" }, this.metadataParams); var url = this.protocol + "//dev.virtualearth.net/REST/v1/Imagery/Metadata/" + this.type + "?" + OpenLayers.Util.getParameterString(params); var script = document.createElement("script"); script.type = "text/javascript"; script.src = url; script.id = this._callbackId; document.getElementsByTagName("head")[0].appendChild(script); }, /** * Method: initLayer * * Sets layer properties according to the metadata provided by the API */ initLayer: function() { var res = this.metadata.resourceSets[0].resources[0]; var url = res.imageUrl.replace("{quadkey}", "${quadkey}"); url = url.replace("{culture}", this.culture); url = url.replace(this.protocolRegex, this.protocol); this.url = []; for (var i=0; i} */ getURL: function(bounds) { if (!this.url) { return; } var xyz = this.getXYZ(bounds), x = xyz.x, y = xyz.y, z = xyz.z; var quadDigits = []; for (var i = z; i > 0; --i) { var digit = '0'; var mask = 1 << (i - 1); if ((x & mask) != 0) { digit++; } if ((y & mask) != 0) { digit++; digit++; } quadDigits.push(digit); } var quadKey = quadDigits.join(""); var url = this.selectUrl('' + x + y + z, this.url); return OpenLayers.String.format(url, {'quadkey': quadKey}); }, /** * Method: updateAttribution * Updates the attribution according to the requirements outlined in * http://gis.638310.n2.nabble.com/Bing-imagery-td5789168.html */ updateAttribution: function() { var metadata = this.metadata; if (!metadata.resourceSets || !this.map || !this.map.center) { return; } var res = metadata.resourceSets[0].resources[0]; var extent = this.map.getExtent().transform( this.map.getProjectionObject(), new OpenLayers.Projection("EPSG:4326") ); var providers = res.imageryProviders || [], zoom = OpenLayers.Util.indexOf(this.serverResolutions, this.getServerResolution()), copyrights = "", provider, i, ii, j, jj, bbox, coverage; for (i=0,ii=providers.length; i= coverage.zoomMin) { copyrights += provider.attribution + " "; } } } var logo = metadata.brandLogoUri.replace(this.protocolRegex, this.protocol); this.attribution = OpenLayers.String.format(this.attributionTemplate, { type: this.type.toLowerCase(), logo: logo, copyrights: copyrights }); this.map && this.map.events.triggerEvent("changelayer", { layer: this, property: "attribution" }); }, /** * Method: setMap */ setMap: function() { OpenLayers.Layer.XYZ.prototype.setMap.apply(this, arguments); this.map.events.register("moveend", this, this.updateAttribution); }, /** * APIMethod: clone * * Parameters: * obj - {Object} * * Returns: * {} An exact clone of this */ clone: function(obj) { if (obj == null) { obj = new OpenLayers.Layer.Bing(this.options); } //get all additions from superclasses obj = OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]); // copy/set any non-init, non-simple values here return obj; }, /** * Method: destroy */ destroy: function() { this.map && this.map.events.unregister("moveend", this, this.updateAttribution); OpenLayers.Layer.XYZ.prototype.destroy.apply(this, arguments); }, CLASS_NAME: "OpenLayers.Layer.Bing" }); /** * Function: OpenLayers.Layer.Bing.processMetadata * This function will be bound to an instance, linked to the global scope with * an id, and called by the JSONP script returned by the API. * * Parameters: * metadata - {Object} metadata as returned by the API */ OpenLayers.Layer.Bing.processMetadata = function(metadata) { this.metadata = metadata; this.initLayer(); var script = document.getElementById(this._callbackId); script.parentNode.removeChild(script); window[this._callbackId] = undefined; // cannot delete from window in IE delete this._callbackId; }; /* ====================================================================== OpenLayers/Handler.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/BaseTypes/Class.js * @requires OpenLayers/Events.js */ /** * Class: OpenLayers.Handler * Base class to construct a higher-level handler for event sequences. All * handlers have activate and deactivate methods. In addition, they have * methods named like browser events. When a handler is activated, any * additional methods named like a browser event is registered as a * listener for the corresponding event. When a handler is deactivated, * those same methods are unregistered as event listeners. * * Handlers also typically have a callbacks object with keys named like * the abstracted events or event sequences that they are in charge of * handling. The controls that wrap handlers define the methods that * correspond to these abstract events - so instead of listening for * individual browser events, they only listen for the abstract events * defined by the handler. * * Handlers are created by controls, which ultimately have the responsibility * of making changes to the the state of the application. Handlers * themselves may make temporary changes, but in general are expected to * return the application in the same state that they found it. */ OpenLayers.Handler = OpenLayers.Class({ /** * Property: id * {String} */ id: null, /** * APIProperty: control * {}. The control that initialized this handler. The * control is assumed to have a valid map property - that map is used * in the handler's own setMap method. */ control: null, /** * Property: map * {} */ map: null, /** * APIProperty: keyMask * {Integer} Use bitwise operators and one or more of the OpenLayers.Handler * constants to construct a keyMask. The keyMask is used by * . If the keyMask matches the combination of keys * down on an event, checkModifiers returns true. * * Example: * (code) * // handler only responds if the Shift key is down * handler.keyMask = OpenLayers.Handler.MOD_SHIFT; * * // handler only responds if Ctrl-Shift is down * handler.keyMask = OpenLayers.Handler.MOD_SHIFT | * OpenLayers.Handler.MOD_CTRL; * (end) */ keyMask: null, /** * Property: active * {Boolean} */ active: false, /** * Property: evt * {Event} This property references the last event handled by the handler. * Note that this property is not part of the stable API. Use of the * evt property should be restricted to controls in the library * or other applications that are willing to update with changes to * the OpenLayers code. */ evt: null, /** * Property: touch * {Boolean} Indicates the support of touch events. When touch events are * started touch will be true and all mouse related listeners will do * nothing. */ touch: false, /** * Constructor: OpenLayers.Handler * Construct a handler. * * Parameters: * control - {} The control that initialized this * handler. The control is assumed to have a valid map property; that * map is used in the handler's own setMap method. If a map property * is present in the options argument it will be used instead. * callbacks - {Object} An object whose properties correspond to abstracted * events or sequences of browser events. The values for these * properties are functions defined by the control that get called by * the handler. * options - {Object} An optional object whose properties will be set on * the handler. */ initialize: function(control, callbacks, options) { OpenLayers.Util.extend(this, options); this.control = control; this.callbacks = callbacks; var map = this.map || control.map; if (map) { this.setMap(map); } this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_"); }, /** * Method: setMap */ setMap: function (map) { this.map = map; }, /** * Method: checkModifiers * Check the keyMask on the handler. If no is set, this always * returns true. If a is set and it matches the combination * of keys down on an event, this returns true. * * Returns: * {Boolean} The keyMask matches the keys down on an event. */ checkModifiers: function (evt) { if(this.keyMask == null) { return true; } /* calculate the keyboard modifier mask for this event */ var keyModifiers = (evt.shiftKey ? OpenLayers.Handler.MOD_SHIFT : 0) | (evt.ctrlKey ? OpenLayers.Handler.MOD_CTRL : 0) | (evt.altKey ? OpenLayers.Handler.MOD_ALT : 0) | (evt.metaKey ? OpenLayers.Handler.MOD_META : 0); /* if it differs from the handler object's key mask, bail out of the event handler */ return (keyModifiers == this.keyMask); }, /** * APIMethod: activate * Turn on the handler. Returns false if the handler was already active. * * Returns: * {Boolean} The handler was activated. */ activate: function() { if(this.active) { return false; } // register for event handlers defined on this class. var events = OpenLayers.Events.prototype.BROWSER_EVENTS; for (var i=0, len=events.length; i will be * true and all mouse related listeners will do nothing. */ startTouch: function() { if (!this.touch) { this.touch = true; var events = [ "mousedown", "mouseup", "mousemove", "click", "dblclick", "mouseout" ]; for (var i=0, len=events.length; i, returns false if any key is down. */ OpenLayers.Handler.MOD_NONE = 0; /** * Constant: OpenLayers.Handler.MOD_SHIFT * If set as the , returns false if Shift is down. */ OpenLayers.Handler.MOD_SHIFT = 1; /** * Constant: OpenLayers.Handler.MOD_CTRL * If set as the , returns false if Ctrl is down. */ OpenLayers.Handler.MOD_CTRL = 2; /** * Constant: OpenLayers.Handler.MOD_ALT * If set as the , returns false if Alt is down. */ OpenLayers.Handler.MOD_ALT = 4; /** * Constant: OpenLayers.Handler.MOD_META * If set as the , returns false if Cmd is down. */ OpenLayers.Handler.MOD_META = 8; /* ====================================================================== OpenLayers/Handler/MouseWheel.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of 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.MouseWheel * Handler for wheel up/down events. * * Inherits from: * - */ OpenLayers.Handler.MouseWheel = OpenLayers.Class(OpenLayers.Handler, { /** * Property: wheelListener * {function} */ wheelListener: null, /** * Property: interval * {Integer} In order to increase server performance, an interval (in * milliseconds) can be set to reduce the number of up/down events * called. If set, a new up/down event will not be set until the * interval has passed. * Defaults to 0, meaning no interval. */ interval: 0, /** * Property: maxDelta * {Integer} Maximum delta to collect before breaking from the current * interval. In cumulative mode, this also limits the maximum delta * returned from the handler. Default is Number.POSITIVE_INFINITY. */ maxDelta: Number.POSITIVE_INFINITY, /** * Property: delta * {Integer} When interval is set, delta collects the mousewheel z-deltas * of the events that occur within the interval. * See also the cumulative option */ delta: 0, /** * Property: cumulative * {Boolean} When interval is set: true to collect all the mousewheel * z-deltas, false to only record the delta direction (positive or * negative) */ cumulative: true, /** * Constructor: OpenLayers.Handler.MouseWheel * * Parameters: * control - {} * callbacks - {Object} An object containing a single function to be * called when the drag operation is finished. * The callback should expect to recieve a single * argument, the point geometry. * options - {Object} */ initialize: function(control, callbacks, options) { OpenLayers.Handler.prototype.initialize.apply(this, arguments); this.wheelListener = OpenLayers.Function.bindAsEventListener( this.onWheelEvent, this ); }, /** * Method: destroy */ destroy: function() { OpenLayers.Handler.prototype.destroy.apply(this, arguments); this.wheelListener = null; }, /** * Mouse ScrollWheel code thanks to http://adomas.org/javascript-mouse-wheel/ */ /** * Method: onWheelEvent * Catch the wheel event and handle it xbrowserly * * Parameters: * e - {Event} */ onWheelEvent: function(e){ // make sure we have a map and check keyboard modifiers if (!this.map || !this.checkModifiers(e)) { return; } // Ride up the element's DOM hierarchy to determine if it or any of // its ancestors was: // * specifically marked as scrollable (CSS overflow property) // * one of our layer divs or a div marked as scrollable // ('olScrollable' CSS class) // * the map div // var overScrollableDiv = false; var allowScroll = false; var overMapDiv = false; var elem = OpenLayers.Event.element(e); while((elem != null) && !overMapDiv && !overScrollableDiv) { if (!overScrollableDiv) { try { var overflow; if (elem.currentStyle) { overflow = elem.currentStyle["overflow"]; } else { var style = document.defaultView.getComputedStyle(elem, null); overflow = style.getPropertyValue("overflow"); } overScrollableDiv = ( overflow && (overflow == "auto") || (overflow == "scroll") ); } catch(err) { //sometimes when scrolling in a popup, this causes // obscure browser error } } if (!allowScroll) { allowScroll = OpenLayers.Element.hasClass(elem, 'olScrollable'); if (!allowScroll) { for (var i = 0, len = this.map.layers.length; i < len; i++) { // Are we in the layer div? Note that we have two cases // here: one is to catch EventPane layers, which have a // pane above the layer (layer.pane) var layer = this.map.layers[i]; if (elem == layer.div || elem == layer.pane) { allowScroll = true; break; } } } } overMapDiv = (elem == this.map.div); elem = elem.parentNode; } // Logic below is the following: // // If we are over a scrollable div or not over the map div: // * do nothing (let the browser handle scrolling) // // otherwise // // If we are over the layer div or a 'olScrollable' div: // * zoom/in out // then // * kill event (so as not to also scroll the page after zooming) // // otherwise // // Kill the event (dont scroll the page if we wheel over the // layerswitcher or the pan/zoom control) // if (!overScrollableDiv && overMapDiv) { if (allowScroll) { var delta = 0; if (e.wheelDelta) { delta = e.wheelDelta; if (delta % 160 === 0) { // opera have steps of 160 instead of 120 delta = delta * 0.75; } delta = delta / 120; } else if (e.detail) { // detail in Firefox on OS X is 1/3 of Windows // so force delta 1 / -1 delta = - (e.detail / Math.abs(e.detail)); } this.delta += delta; window.clearTimeout(this._timeoutId); if(this.interval && Math.abs(this.delta) < this.maxDelta) { // store e because window.event might change during delay var evt = OpenLayers.Util.extend({}, e); this._timeoutId = window.setTimeout( OpenLayers.Function.bind(function(){ this.wheelZoom(evt); }, this), this.interval ); } else { this.wheelZoom(e); } } OpenLayers.Event.stop(e); } }, /** * Method: wheelZoom * Given the wheel event, we carry out the appropriate zooming in or out, * based on the 'wheelDelta' or 'detail' property of the event. * * Parameters: * e - {Event} */ wheelZoom: function(e) { var delta = this.delta; this.delta = 0; if (delta) { e.xy = this.map.events.getMousePosition(e); if (delta < 0) { this.callback("down", [e, this.cumulative ? Math.max(-this.maxDelta, delta) : -1]); } else { this.callback("up", [e, this.cumulative ? Math.min(this.maxDelta, delta) : 1]); } } }, /** * Method: activate */ activate: function (evt) { if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) { //register mousewheel events specifically on the window and document var wheelListener = this.wheelListener; OpenLayers.Event.observe(window, "DOMMouseScroll", wheelListener); OpenLayers.Event.observe(window, "mousewheel", wheelListener); OpenLayers.Event.observe(document, "mousewheel", wheelListener); return true; } else { return false; } }, /** * Method: deactivate */ deactivate: function (evt) { if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) { // unregister mousewheel events specifically on the window and document var wheelListener = this.wheelListener; OpenLayers.Event.stopObserving(window, "DOMMouseScroll", wheelListener); OpenLayers.Event.stopObserving(window, "mousewheel", wheelListener); OpenLayers.Event.stopObserving(document, "mousewheel", wheelListener); return true; } else { return false; } }, CLASS_NAME: "OpenLayers.Handler.MouseWheel" }); /* ====================================================================== OpenLayers/Geometry/MultiLineString.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Geometry/Collection.js * @requires OpenLayers/Geometry/LineString.js */ /** * Class: OpenLayers.Geometry.MultiLineString * A MultiLineString is a geometry with multiple * components. * * Inherits from: * - * - */ OpenLayers.Geometry.MultiLineString = OpenLayers.Class( OpenLayers.Geometry.Collection, { /** * Property: componentTypes * {Array(String)} An array of class names representing the types of * components that the collection can include. A null value means the * component types are not restricted. */ componentTypes: ["OpenLayers.Geometry.LineString"], /** * Constructor: OpenLayers.Geometry.MultiLineString * Constructor for a MultiLineString Geometry. * * Parameters: * components - {Array()} * */ /** * Method: split * Use this geometry (the source) to attempt to split a target geometry. * * Parameters: * geometry - {} The target geometry. * options - {Object} Properties of this object will be used to determine * how the split is conducted. * * Valid options: * mutual - {Boolean} Split the source geometry in addition to the target * geometry. Default is false. * edge - {Boolean} Allow splitting when only edges intersect. Default is * true. If false, a vertex on the source must be within the tolerance * distance of the intersection to be considered a split. * tolerance - {Number} If a non-null value is provided, intersections * within the tolerance distance of an existing vertex on the source * will be assumed to occur at the vertex. * * Returns: * {Array} A list of geometries (of this same type as the target) that * result from splitting the target with the source geometry. The * source and target geometry will remain unmodified. If no split * results, null will be returned. If mutual is true and a split * results, return will be an array of two arrays - the first will be * all geometries that result from splitting the source geometry and * the second will be all geometries that result from splitting the * target geometry. */ split: function(geometry, options) { var results = null; var mutual = options && options.mutual; var splits, sourceLine, sourceLines, sourceSplit, targetSplit; var sourceParts = []; var targetParts = [geometry]; for(var i=0, len=this.components.length; i 1) { sourceSplit = true; } else { sourceParts = []; } if(targetParts && targetParts.length > 1) { targetSplit = true; } else { targetParts = []; } if(sourceSplit || targetSplit) { if(mutual) { results = [sourceParts, targetParts]; } else { results = targetParts; } } return results; }, /** * Method: splitWith * Split this geometry (the target) with the given geometry (the source). * * Parameters: * geometry - {} A geometry used to split this * geometry (the source). * options - {Object} Properties of this object will be used to determine * how the split is conducted. * * Valid options: * mutual - {Boolean} Split the source geometry in addition to the target * geometry. Default is false. * edge - {Boolean} Allow splitting when only edges intersect. Default is * true. If false, a vertex on the source must be within the tolerance * distance of the intersection to be considered a split. * tolerance - {Number} If a non-null value is provided, intersections * within the tolerance distance of an existing vertex on the source * will be assumed to occur at the vertex. * * Returns: * {Array} A list of geometries (of this same type as the target) that * result from splitting the target with the source geometry. The * source and target geometry will remain unmodified. If no split * results, null will be returned. If mutual is true and a split * results, return will be an array of two arrays - the first will be * all geometries that result from splitting the source geometry and * the second will be all geometries that result from splitting the * target geometry. */ splitWith: function(geometry, options) { var results = null; var mutual = options && options.mutual; var splits, targetLine, sourceLines, sourceSplit, targetSplit, sourceParts, targetParts; if(geometry instanceof OpenLayers.Geometry.LineString) { targetParts = []; sourceParts = [geometry]; for(var i=0, len=this.components.length; i 1) { sourceSplit = true; } else { sourceParts = []; } if(targetParts && targetParts.length > 1) { targetSplit = true; } else { targetParts = []; } if(sourceSplit || targetSplit) { if(mutual) { results = [sourceParts, targetParts]; } else { results = targetParts; } } return results; }, CLASS_NAME: "OpenLayers.Geometry.MultiLineString" }); /* ====================================================================== OpenLayers/Renderer.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/BaseTypes/Class.js */ /** * Class: OpenLayers.Renderer * This is the base class for all renderers. * * This is based on a merger code written by Paul Spencer and Bertil Chapuis. * It is largely composed of virtual functions that are to be implemented * in technology-specific subclasses, but there is some generic code too. * * The functions that *are* implemented here merely deal with the maintenance * of the size and extent variables, as well as the cached 'resolution' * value. * * A note to the user that all subclasses should use getResolution() instead * of directly accessing this.resolution in order to correctly use the * cacheing system. * */ OpenLayers.Renderer = OpenLayers.Class({ /** * Property: container * {DOMElement} */ container: null, /** * Property: root * {DOMElement} */ root: null, /** * Property: extent * {} */ extent: null, /** * Property: locked * {Boolean} If the renderer is currently in a state where many things * are changing, the 'locked' property is set to true. This means * that renderers can expect at least one more drawFeature event to be * called with the 'locked' property set to 'true': In some renderers, * this might make sense to use as a 'only update local information' * flag. */ locked: false, /** * Property: size * {} */ size: null, /** * Property: resolution * {Float} cache of current map resolution */ resolution: null, /** * Property: map * {} Reference to the map -- this is set in Vector's setMap() */ map: null, /** * Property: featureDx * {Number} Feature offset in x direction. Will be calculated for and * applied to the current feature while rendering (see * ). */ featureDx: 0, /** * Constructor: OpenLayers.Renderer * * Parameters: * containerID - {} * options - {Object} options for this renderer. See sublcasses for * supported options. */ initialize: function(containerID, options) { this.container = OpenLayers.Util.getElement(containerID); OpenLayers.Util.extend(this, options); }, /** * APIMethod: destroy */ destroy: function() { this.container = null; this.extent = null; this.size = null; this.resolution = null; this.map = null; }, /** * APIMethod: supported * This should be overridden by specific subclasses * * Returns: * {Boolean} Whether or not the browser supports the renderer class */ supported: function() { return false; }, /** * Method: setExtent * Set the visible part of the layer. * * Resolution has probably changed, so we nullify the resolution * cache (this.resolution) -- this way it will be re-computed when * next it is needed. * We nullify the resolution cache (this.resolution) if resolutionChanged * is set to true - this way it will be re-computed on the next * getResolution() request. * * Parameters: * extent - {} * resolutionChanged - {Boolean} * * Returns: * {Boolean} true to notify the layer that the new extent does not exceed * the coordinate range, and the features will not need to be redrawn. * False otherwise. */ setExtent: function(extent, resolutionChanged) { this.extent = extent.clone(); if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) { var ratio = extent.getWidth() / this.map.getExtent().getWidth(), extent = extent.scale(1 / ratio); this.extent = extent.wrapDateLine(this.map.getMaxExtent()).scale(ratio); } if (resolutionChanged) { this.resolution = null; } return true; }, /** * Method: setSize * Sets the size of the drawing surface. * * Resolution has probably changed, so we nullify the resolution * cache (this.resolution) -- this way it will be re-computed when * next it is needed. * * Parameters: * size - {} */ setSize: function(size) { this.size = size.clone(); this.resolution = null; }, /** * Method: getResolution * Uses cached copy of resolution if available to minimize computing * * Returns: * {Float} The current map's resolution */ getResolution: function() { this.resolution = this.resolution || this.map.getResolution(); return this.resolution; }, /** * Method: drawFeature * Draw the feature. The optional style argument can be used * to override the feature's own style. This method should only * be called from layer.drawFeature(). * * Parameters: * feature - {} * style - {} * * Returns: * {Boolean} true if the feature has been drawn completely, false if not, * undefined if the feature had no geometry */ drawFeature: function(feature, style) { if(style == null) { style = feature.style; } if (feature.geometry) { var bounds = feature.geometry.getBounds(); if(bounds) { var worldBounds; if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) { worldBounds = this.map.getMaxExtent(); } if (!bounds.intersectsBounds(this.extent, {worldBounds: worldBounds})) { style = {display: "none"}; } else { this.calculateFeatureDx(bounds, worldBounds); } var rendered = this.drawGeometry(feature.geometry, style, feature.id); if(style.display != "none" && style.label && rendered !== false) { var location = feature.geometry.getCentroid(); if(style.labelXOffset || style.labelYOffset) { var xOffset = isNaN(style.labelXOffset) ? 0 : style.labelXOffset; var yOffset = isNaN(style.labelYOffset) ? 0 : style.labelYOffset; var res = this.getResolution(); location.move(xOffset*res, yOffset*res); } this.drawText(feature.id, style, location); } else { this.removeText(feature.id); } return rendered; } } }, /** * Method: calculateFeatureDx * {Number} Calculates the feature offset in x direction. Looking at the * center of the feature bounds and the renderer extent, we calculate how * many world widths the two are away from each other. This distance is * used to shift the feature as close as possible to the center of the * current enderer extent, which ensures that the feature is visible in the * current viewport. * * Parameters: * bounds - {} Bounds of the feature * worldBounds - {} Bounds of the world */ calculateFeatureDx: function(bounds, worldBounds) { this.featureDx = 0; if (worldBounds) { var worldWidth = worldBounds.getWidth(), rendererCenterX = (this.extent.left + this.extent.right) / 2, featureCenterX = (bounds.left + bounds.right) / 2, worldsAway = Math.round((featureCenterX - rendererCenterX) / worldWidth); this.featureDx = worldsAway * worldWidth; } }, /** * Method: drawGeometry * * Draw a geometry. This should only be called from the renderer itself. * Use layer.drawFeature() from outside the renderer. * virtual function * * Parameters: * geometry - {} * style - {Object} * featureId - {} */ drawGeometry: function(geometry, style, featureId) {}, /** * Method: drawText * Function for drawing text labels. * This method is only called by the renderer itself. * * Parameters: * featureId - {String} * style - * location - {} */ drawText: function(featureId, style, location) {}, /** * Method: removeText * Function for removing text labels. * This method is only called by the renderer itself. * * Parameters: * featureId - {String} */ removeText: function(featureId) {}, /** * Method: clear * Clear all vectors from the renderer. * virtual function. */ clear: function() {}, /** * Method: getFeatureIdFromEvent * Returns a feature id from an event on the renderer. * How this happens is specific to the renderer. This should be * called from layer.getFeatureFromEvent(). * Virtual function. * * Parameters: * evt - {} * * Returns: * {String} A feature id or undefined. */ getFeatureIdFromEvent: function(evt) {}, /** * Method: eraseFeatures * This is called by the layer to erase features * * Parameters: * features - {Array()} */ eraseFeatures: function(features) { if(!(OpenLayers.Util.isArray(features))) { features = [features]; } for(var i=0, len=features.length; i} * featureId - {String} */ eraseGeometry: function(geometry, featureId) {}, /** * Method: moveRoot * moves this renderer's root to a (different) renderer. * To be implemented by subclasses that require a common renderer root for * feature selection. * * Parameters: * renderer - {} target renderer for the moved root */ moveRoot: function(renderer) {}, /** * Method: getRenderLayerId * Gets the layer that this renderer's output appears on. If moveRoot was * used, this will be different from the id of the layer containing the * features rendered by this renderer. * * Returns: * {String} the id of the output layer. */ getRenderLayerId: function() { return this.container.id; }, /** * Method: applyDefaultSymbolizer * * Parameters: * symbolizer - {Object} * * Returns: * {Object} */ applyDefaultSymbolizer: function(symbolizer) { var result = OpenLayers.Util.extend({}, OpenLayers.Renderer.defaultSymbolizer); if(symbolizer.stroke === false) { delete result.strokeWidth; delete result.strokeColor; } if(symbolizer.fill === false) { delete result.fillColor; } OpenLayers.Util.extend(result, symbolizer); return result; }, CLASS_NAME: "OpenLayers.Renderer" }); /** * Constant: OpenLayers.Renderer.defaultSymbolizer * {Object} Properties from this symbolizer will be applied to symbolizers * with missing properties. This can also be used to set a global * symbolizer default in OpenLayers. To be SLD 1.x compliant, add the * following code before rendering any vector features: * (code) * OpenLayers.Renderer.defaultSymbolizer = { * fillColor: "#808080", * fillOpacity: 1, * strokeColor: "#000000", * strokeOpacity: 1, * strokeWidth: 1, * pointRadius: 3, * graphicName: "square" * }; * (end) */ OpenLayers.Renderer.defaultSymbolizer = { fillColor: "#000000", strokeColor: "#000000", strokeWidth: 2, fillOpacity: 1, strokeOpacity: 1, pointRadius: 0, labelAlign: 'cm' }; /** * Constant: OpenLayers.Renderer.symbol * Coordinate arrays for well known (named) symbols. */ OpenLayers.Renderer.symbol = { "star": [350,75, 379,161, 469,161, 397,215, 423,301, 350,250, 277,301, 303,215, 231,161, 321,161, 350,75], "cross": [4,0, 6,0, 6,4, 10,4, 10,6, 6,6, 6,10, 4,10, 4,6, 0,6, 0,4, 4,4, 4,0], "x": [0,0, 25,0, 50,35, 75,0, 100,0, 65,50, 100,100, 75,100, 50,65, 25,100, 0,100, 35,50, 0,0], "square": [0,0, 0,1, 1,1, 1,0, 0,0], "triangle": [0,10, 10,10, 5,0, 0,10] }; /* ====================================================================== OpenLayers/Renderer/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} This is an array of node id's stored in the * order that they should show up on screen. Id's higher up in the * array (higher array index) represent nodes with higher z-indeces. */ order: null, /** * Property: indices * {Object} This is a hash that maps node ids to their z-index value * stored in the indexer. This is done to make finding a nodes z-index * value O(1). */ indices: null, /** * Property: compare * {Function} This is the function used to determine placement of * of a new node within the indexer. If null, this defaults to to * the Z_ORDER_DRAWING_ORDER comparison method. */ compare: null, /** * APIMethod: initialize * Create a new indexer with * * Parameters: * yOrdering - {Boolean} Whether to use y-ordering. */ initialize: function(yOrdering) { this.compare = yOrdering ? OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_Y_ORDER : OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_DRAWING_ORDER; this.clear(); }, /** * APIMethod: insert * Insert a new node into the indexer. In order to find the correct * positioning for the node to be inserted, this method uses a binary * search. This makes inserting O(log(n)). * * Parameters: * newNode - {DOMElement} The new node to be inserted. * * Returns * {DOMElement} the node before which we should insert our newNode, or * null if newNode can just be appended. */ insert: function(newNode) { // If the node is known to the indexer, remove it so we can // recalculate where it should go. if (this.exists(newNode)) { this.remove(newNode); } var nodeId = newNode.id; this.determineZIndex(newNode); var leftIndex = -1; var rightIndex = this.order.length; var middle; while (rightIndex - leftIndex > 1) { middle = parseInt((leftIndex + rightIndex) / 2); var placement = this.compare(this, newNode, OpenLayers.Util.getElement(this.order[middle])); if (placement > 0) { leftIndex = middle; } else { rightIndex = middle; } } this.order.splice(rightIndex, 0, nodeId); this.indices[nodeId] = this.getZIndex(newNode); // If the new node should be before another in the index // order, return the node before which we have to insert the new one; // else, return null to indicate that the new node can be appended. return this.getNextElement(rightIndex); }, /** * APIMethod: remove * * Parameters: * node - {DOMElement} The node to be removed. */ remove: function(node) { var nodeId = node.id; var arrayIndex = OpenLayers.Util.indexOf(this.order, nodeId); if (arrayIndex >= 0) { // Remove it from the order array, as well as deleting the node // from the indeces hash. this.order.splice(arrayIndex, 1); delete this.indices[nodeId]; // Reset the maxium z-index based on the last item in the // order array. if (this.order.length > 0) { var lastId = this.order[this.order.length - 1]; this.maxZIndex = this.indices[lastId]; } else { this.maxZIndex = 0; } } }, /** * APIMethod: clear */ clear: function() { this.order = []; this.indices = {}; this.maxZIndex = 0; }, /** * APIMethod: exists * * Parameters: * node - {DOMElement} The node to test for existence. * * Returns: * {Boolean} Whether or not the node exists in the indexer? */ exists: function(node) { return (this.indices[node.id] != null); }, /** * APIMethod: getZIndex * Get the z-index value for the current node from the node data itself. * * Parameters: * node - {DOMElement} The node whose z-index to get. * * Returns: * {Integer} The z-index value for the specified node (from the node * data itself). */ getZIndex: function(node) { return node._style.graphicZIndex; }, /** * Method: determineZIndex * Determine the z-index for the current node if there isn't one, * and set the maximum value if we've found a new maximum. * * Parameters: * node - {DOMElement} */ determineZIndex: function(node) { var zIndex = node._style.graphicZIndex; // Everything must have a zIndex. If none is specified, // this means the user *must* (hint: assumption) want this // node to succomb to drawing order. To enforce drawing order // over all indexing methods, we'll create a new z-index that's // greater than any currently in the indexer. if (zIndex == null) { zIndex = this.maxZIndex; node._style.graphicZIndex = zIndex; } else if (zIndex > this.maxZIndex) { this.maxZIndex = zIndex; } }, /** * APIMethod: getNextElement * Get the next element in the order stack. * * Parameters: * index - {Integer} The index of the current node in this.order. * * Returns: * {DOMElement} the node following the index passed in, or * null. */ getNextElement: function(index) { var nextIndex = index + 1; if (nextIndex < this.order.length) { var nextElement = OpenLayers.Util.getElement(this.order[nextIndex]); if (nextElement == undefined) { nextElement = this.getNextElement(nextIndex); } return nextElement; } else { return null; } }, CLASS_NAME: "OpenLayers.ElementsIndexer" }); /** * Namespace: OpenLayers.ElementsIndexer.IndexingMethods * These are the compare methods for figuring out where a new node should be * placed within the indexer. These methods are very similar to general * sorting methods in that they return -1, 0, and 1 to specify the * direction in which new nodes fall in the ordering. */ OpenLayers.ElementsIndexer.IndexingMethods = { /** * Method: Z_ORDER * This compare method is used by other comparison methods. * It can be used individually for ordering, but is not recommended, * because it doesn't subscribe to drawing order. * * Parameters: * indexer - {} * newNode - {DOMElement} * nextNode - {DOMElement} * * Returns: * {Integer} */ Z_ORDER: function(indexer, newNode, nextNode) { var newZIndex = indexer.getZIndex(newNode); var returnVal = 0; if (nextNode) { var nextZIndex = indexer.getZIndex(nextNode); returnVal = newZIndex - nextZIndex; } return returnVal; }, /** * APIMethod: Z_ORDER_DRAWING_ORDER * This method orders nodes by their z-index, but does so in a way * that, if there are other nodes with the same z-index, the newest * drawn will be the front most within that z-index. This is the * default indexing method. * * Parameters: * indexer - {} * newNode - {DOMElement} * nextNode - {DOMElement} * * Returns: * {Integer} */ Z_ORDER_DRAWING_ORDER: function(indexer, newNode, nextNode) { var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER( indexer, newNode, nextNode ); // Make Z_ORDER subscribe to drawing order by pushing it above // all of the other nodes with the same z-index. if (nextNode && returnVal == 0) { returnVal = 1; } return returnVal; }, /** * APIMethod: Z_ORDER_Y_ORDER * This one should really be called Z_ORDER_Y_ORDER_DRAWING_ORDER, as it * best describes which ordering methods have precedence (though, the * name would be too long). This method orders nodes by their z-index, * but does so in a way that, if there are other nodes with the same * z-index, the nodes with the lower y position will be "closer" than * those with a higher y position. If two nodes have the exact same y * position, however, then this method will revert to using drawing * order to decide placement. * * Parameters: * indexer - {} * newNode - {DOMElement} * nextNode - {DOMElement} * * Returns: * {Integer} */ Z_ORDER_Y_ORDER: function(indexer, newNode, nextNode) { var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER( indexer, newNode, nextNode ); if (nextNode && returnVal === 0) { var result = nextNode._boundsBottom - newNode._boundsBottom; returnVal = (result === 0) ? 1 : result; } return returnVal; } }; /** * Class: OpenLayers.Renderer.Elements * This is another virtual class in that it should never be instantiated by * itself as a Renderer. It exists because there is *tons* of shared * functionality between different vector libraries which use nodes/elements * as a base for rendering vectors. * * The highlevel bits of code that are implemented here are the adding and * removing of geometries, which is essentially the same for any * element-based renderer. The details of creating each node and drawing the * paths are of course different, but the machinery is the same. * * Inherits: * - */ OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, { /** * Property: rendererRoot * {DOMElement} */ rendererRoot: null, /** * Property: root * {DOMElement} */ root: null, /** * Property: vectorRoot * {DOMElement} */ vectorRoot: null, /** * Property: textRoot * {DOMElement} */ textRoot: null, /** * Property: xmlns * {String} */ xmlns: null, /** * Property: xOffset * {Number} Offset to apply to the renderer viewport translation in x * direction. If the renderer extent's center is on the right of the * dateline (i.e. exceeds the world bounds), we shift the viewport to the * left by one world width. This avoids that features disappear from the * map viewport. Because our dateline handling logic in other places * ensures that extents crossing the dateline always have a center * exceeding the world bounds on the left, we need this offset to make sure * that the same is true for the renderer extent in pixel space as well. */ xOffset: 0, /** * Property: rightOfDateLine * {Boolean} Keeps track of the location of the map extent relative to the * date line. The method compares this value (which is the one * from the previous call) with the current position of the map * extent relative to the date line and updates the xOffset when the extent * has moved from one side of the date line to the other. */ /** * Property: Indexer * {} An instance of OpenLayers.ElementsIndexer * created upon initialization if the zIndexing or yOrdering options * passed to this renderer's constructor are set to true. */ indexer: null, /** * Constant: BACKGROUND_ID_SUFFIX * {String} */ BACKGROUND_ID_SUFFIX: "_background", /** * Constant: LABEL_ID_SUFFIX * {String} */ LABEL_ID_SUFFIX: "_label", /** * Constant: LABEL_OUTLINE_SUFFIX * {String} */ LABEL_OUTLINE_SUFFIX: "_outline", /** * Constructor: OpenLayers.Renderer.Elements * * Parameters: * containerID - {String} * options - {Object} options for this renderer. * * Supported options are: * yOrdering - {Boolean} Whether to use y-ordering * zIndexing - {Boolean} Whether to use z-indexing. Will be ignored * if yOrdering is set to true. */ initialize: function(containerID, options) { OpenLayers.Renderer.prototype.initialize.apply(this, arguments); this.rendererRoot = this.createRenderRoot(); this.root = this.createRoot("_root"); this.vectorRoot = this.createRoot("_vroot"); this.textRoot = this.createRoot("_troot"); this.root.appendChild(this.vectorRoot); this.root.appendChild(this.textRoot); this.rendererRoot.appendChild(this.root); this.container.appendChild(this.rendererRoot); if(options && (options.zIndexing || options.yOrdering)) { this.indexer = new OpenLayers.ElementsIndexer(options.yOrdering); } }, /** * Method: destroy */ destroy: function() { this.clear(); this.rendererRoot = null; this.root = null; this.xmlns = null; OpenLayers.Renderer.prototype.destroy.apply(this, arguments); }, /** * Method: clear * Remove all the elements from the root */ clear: function() { var child; var root = this.vectorRoot; if (root) { while (child = root.firstChild) { root.removeChild(child); } } root = this.textRoot; if (root) { while (child = root.firstChild) { root.removeChild(child); } } if (this.indexer) { this.indexer.clear(); } }, /** * Method: setExtent * Set the visible part of the layer. * * Parameters: * extent - {} * resolutionChanged - {Boolean} * * Returns: * {Boolean} true to notify the layer that the new extent does not exceed * the coordinate range, and the features will not need to be redrawn. * False otherwise. */ setExtent: function(extent, resolutionChanged) { var coordSysUnchanged = OpenLayers.Renderer.prototype.setExtent.apply(this, arguments); var resolution = this.getResolution(); if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) { var rightOfDateLine, ratio = extent.getWidth() / this.map.getExtent().getWidth(), extent = extent.scale(1 / ratio), world = this.map.getMaxExtent(); if (world.right > extent.left && world.right < extent.right) { rightOfDateLine = true; } else if (world.left > extent.left && world.left < extent.right) { rightOfDateLine = false; } if (rightOfDateLine !== this.rightOfDateLine || resolutionChanged) { coordSysUnchanged = false; this.xOffset = rightOfDateLine === true ? world.getWidth() / resolution : 0; } this.rightOfDateLine = rightOfDateLine; } return coordSysUnchanged; }, /** * Method: getNodeType * This function is in charge of asking the specific renderer which type * of node to create for the given geometry and style. All geometries * in an Elements-based renderer consist of one node and some * attributes. We have the nodeFactory() function which creates a node * for us, but it takes a 'type' as input, and that is precisely what * this function tells us. * * Parameters: * geometry - {} * style - {Object} * * Returns: * {String} The corresponding node type for the specified geometry */ getNodeType: function(geometry, style) { }, /** * Method: drawGeometry * Draw the geometry, creating new nodes, setting paths, setting style, * setting featureId on the node. This method should only be called * by the renderer itself. * * Parameters: * geometry - {} * style - {Object} * featureId - {String} * * Returns: * {Boolean} true if the geometry has been drawn completely; null if * incomplete; false otherwise */ drawGeometry: function(geometry, style, featureId) { var className = geometry.CLASS_NAME; var rendered = true; if ((className == "OpenLayers.Geometry.Collection") || (className == "OpenLayers.Geometry.MultiPoint") || (className == "OpenLayers.Geometry.MultiLineString") || (className == "OpenLayers.Geometry.MultiPolygon")) { for (var i = 0, len=geometry.components.length; i} * style - {Object} * featureId - {String} * * Returns: * {Boolean} true if the complete geometry could be drawn, null if parts of * the geometry could not be drawn, false otherwise */ redrawNode: function(id, geometry, style, featureId) { style = this.applyDefaultSymbolizer(style); // Get the node if it's already on the map. var node = this.nodeFactory(id, this.getNodeType(geometry, style)); // Set the data for the node, then draw it. node._featureId = featureId; node._boundsBottom = geometry.getBounds().bottom; node._geometryClass = geometry.CLASS_NAME; node._style = style; var drawResult = this.drawGeometryNode(node, geometry, style); if(drawResult === false) { return false; } node = drawResult.node; // Insert the node into the indexer so it can show us where to // place it. Note that this operation is O(log(n)). If there's a // performance problem (when dragging, for instance) this is // likely where it would be. if (this.indexer) { var insert = this.indexer.insert(node); if (insert) { this.vectorRoot.insertBefore(node, insert); } else { this.vectorRoot.appendChild(node); } } else { // if there's no indexer, simply append the node to root, // but only if the node is a new one if (node.parentNode !== this.vectorRoot){ this.vectorRoot.appendChild(node); } } this.postDraw(node); return drawResult.complete; }, /** * Method: redrawBackgroundNode * Redraws the node using special 'background' style properties. Basically * just calls redrawNode(), but instead of directly using the * 'externalGraphic', 'graphicXOffset', 'graphicYOffset', and * 'graphicZIndex' properties directly from the specified 'style' * parameter, we create a new style object and set those properties * from the corresponding 'background'-prefixed properties from * specified 'style' parameter. * * Parameters: * id - {String} * geometry - {} * style - {Object} * featureId - {String} * * Returns: * {Boolean} true if the complete geometry could be drawn, null if parts of * the geometry could not be drawn, false otherwise */ redrawBackgroundNode: function(id, geometry, style, featureId) { var backgroundStyle = OpenLayers.Util.extend({}, style); // Set regular style attributes to apply to the background styles. backgroundStyle.externalGraphic = backgroundStyle.backgroundGraphic; backgroundStyle.graphicXOffset = backgroundStyle.backgroundXOffset; backgroundStyle.graphicYOffset = backgroundStyle.backgroundYOffset; backgroundStyle.graphicZIndex = backgroundStyle.backgroundGraphicZIndex; backgroundStyle.graphicWidth = backgroundStyle.backgroundWidth || backgroundStyle.graphicWidth; backgroundStyle.graphicHeight = backgroundStyle.backgroundHeight || backgroundStyle.graphicHeight; // Erase background styles. backgroundStyle.backgroundGraphic = null; backgroundStyle.backgroundXOffset = null; backgroundStyle.backgroundYOffset = null; backgroundStyle.backgroundGraphicZIndex = null; return this.redrawNode( id + this.BACKGROUND_ID_SUFFIX, geometry, backgroundStyle, null ); }, /** * Method: drawGeometryNode * Given a node, draw a geometry on the specified layer. * node and geometry are required arguments, style is optional. * This method is only called by the render itself. * * Parameters: * node - {DOMElement} * geometry - {} * style - {Object} * * Returns: * {Object} a hash with properties "node" (the drawn node) and "complete" * (null if parts of the geometry could not be drawn, false if nothing * could be drawn) */ drawGeometryNode: function(node, geometry, style) { style = style || node._style; var options = { 'isFilled': style.fill === undefined ? true : style.fill, 'isStroked': style.stroke === undefined ? !!style.strokeWidth : style.stroke }; var drawn; switch (geometry.CLASS_NAME) { case "OpenLayers.Geometry.Point": if(style.graphic === false) { options.isFilled = false; options.isStroked = false; } drawn = this.drawPoint(node, geometry); break; case "OpenLayers.Geometry.LineString": options.isFilled = false; drawn = this.drawLineString(node, geometry); break; case "OpenLayers.Geometry.LinearRing": drawn = this.drawLinearRing(node, geometry); break; case "OpenLayers.Geometry.Polygon": drawn = this.drawPolygon(node, geometry); break; case "OpenLayers.Geometry.Rectangle": drawn = this.drawRectangle(node, geometry); break; default: break; } node._options = options; //set style //TBD simplify this if (drawn != false) { return { node: this.setStyle(node, style, options, geometry), complete: drawn }; } else { return false; } }, /** * Method: postDraw * Things that have do be done after the geometry node is appended * to its parent node. To be overridden by subclasses. * * Parameters: * node - {DOMElement} */ postDraw: function(node) {}, /** * Method: drawPoint * Virtual function for drawing Point Geometry. * Should be implemented by subclasses. * This method is only called by the renderer itself. * * Parameters: * node - {DOMElement} * geometry - {} * * Returns: * {DOMElement} or false if the renderer could not draw the point */ drawPoint: function(node, geometry) {}, /** * Method: drawLineString * Virtual function for drawing LineString Geometry. * Should be implemented by subclasses. * This method is only called by the renderer itself. * * Parameters: * node - {DOMElement} * geometry - {} * * Returns: * {DOMElement} or null if the renderer could not draw all components of * the linestring, or false if nothing could be drawn */ drawLineString: function(node, geometry) {}, /** * Method: drawLinearRing * Virtual function for drawing LinearRing Geometry. * Should be implemented by subclasses. * This method is only called by the renderer itself. * * Parameters: * node - {DOMElement} * geometry - {} * * Returns: * {DOMElement} or null if the renderer could not draw all components * of the linear ring, or false if nothing could be drawn */ drawLinearRing: function(node, geometry) {}, /** * Method: drawPolygon * Virtual function for drawing Polygon Geometry. * Should be implemented by subclasses. * This method is only called by the renderer itself. * * Parameters: * node - {DOMElement} * geometry - {} * * Returns: * {DOMElement} or null if the renderer could not draw all components * of the polygon, or false if nothing could be drawn */ drawPolygon: function(node, geometry) {}, /** * Method: drawRectangle * Virtual function for drawing Rectangle Geometry. * Should be implemented by subclasses. * This method is only called by the renderer itself. * * Parameters: * node - {DOMElement} * geometry - {} * * Returns: * {DOMElement} or false if the renderer could not draw the rectangle */ drawRectangle: function(node, geometry) {}, /** * Method: drawCircle * Virtual function for drawing Circle Geometry. * Should be implemented by subclasses. * This method is only called by the renderer itself. * * Parameters: * node - {DOMElement} * geometry - {} * * Returns: * {DOMElement} or false if the renderer could not draw the circle */ drawCircle: function(node, geometry) {}, /** * Method: removeText * Removes a label * * Parameters: * featureId - {String} */ removeText: function(featureId) { var label = document.getElementById(featureId + this.LABEL_ID_SUFFIX); if (label) { this.textRoot.removeChild(label); } var outline = document.getElementById(featureId + this.LABEL_OUTLINE_SUFFIX); if (outline) { this.textRoot.removeChild(outline); } }, /** * Method: getFeatureIdFromEvent * * Parameters: * evt - {Object} An object * * Returns: * {String} A feature id or undefined. */ getFeatureIdFromEvent: function(evt) { var target = evt.target; var useElement = target && target.correspondingUseElement; var node = useElement ? useElement : (target || evt.srcElement); return node._featureId; }, /** * Method: eraseGeometry * Erase a geometry from the renderer. In the case of a multi-geometry, * we cycle through and recurse on ourselves. Otherwise, we look for a * node with the geometry.id, destroy its geometry, and remove it from * the DOM. * * Parameters: * geometry - {} * featureId - {String} */ eraseGeometry: function(geometry, featureId) { if ((geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPoint") || (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiLineString") || (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPolygon") || (geometry.CLASS_NAME == "OpenLayers.Geometry.Collection")) { for (var i=0, len=geometry.components.length; i} target renderer for the moved root */ moveRoot: function(renderer) { var root = this.root; if(renderer.root.parentNode == this.rendererRoot) { root = renderer.root; } root.parentNode.removeChild(root); renderer.rendererRoot.appendChild(root); }, /** * Method: getRenderLayerId * Gets the layer that this renderer's output appears on. If moveRoot was * used, this will be different from the id of the layer containing the * features rendered by this renderer. * * Returns: * {String} the id of the output layer. */ getRenderLayerId: function() { return this.root.parentNode.parentNode.id; }, /** * Method: isComplexSymbol * Determines if a symbol cannot be rendered using drawCircle * * Parameters: * graphicName - {String} * * Returns * {Boolean} true if the symbol is complex, false if not */ isComplexSymbol: function(graphicName) { return (graphicName != "circle") && !!graphicName; }, CLASS_NAME: "OpenLayers.Renderer.Elements" }); /* ====================================================================== OpenLayers/Control.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/BaseTypes/Class.js */ /** * Class: OpenLayers.Control * Controls affect the display or behavior of the map. They allow everything * from panning and zooming to displaying a scale indicator. Controls by * default are added to the map they are contained within however it is * possible to add a control to an external div by passing the div in the * options parameter. * * Example: * The following example shows how to add many of the common controls * to a map. * * > var map = new OpenLayers.Map('map', { controls: [] }); * > * > map.addControl(new OpenLayers.Control.PanZoomBar()); * > map.addControl(new OpenLayers.Control.LayerSwitcher({'ascending':false})); * > map.addControl(new OpenLayers.Control.Permalink()); * > map.addControl(new OpenLayers.Control.Permalink('permalink')); * > map.addControl(new OpenLayers.Control.MousePosition()); * > map.addControl(new OpenLayers.Control.OverviewMap()); * > map.addControl(new OpenLayers.Control.KeyboardDefaults()); * * The next code fragment is a quick example of how to intercept * shift-mouse click to display the extent of the bounding box * dragged out by the user. Usually controls are not created * in exactly this manner. See the source for a more complete * example: * * > var control = new OpenLayers.Control(); * > OpenLayers.Util.extend(control, { * > draw: function () { * > // this Handler.Box will intercept the shift-mousedown * > // before Control.MouseDefault gets to see it * > this.box = new OpenLayers.Handler.Box( control, * > {"done": this.notice}, * > {keyMask: OpenLayers.Handler.MOD_SHIFT}); * > this.box.activate(); * > }, * > * > notice: function (bounds) { * > OpenLayers.Console.userError(bounds); * > } * > }); * > map.addControl(control); * */ OpenLayers.Control = OpenLayers.Class({ /** * Property: id * {String} */ id: null, /** * Property: map * {} this gets set in the addControl() function in * OpenLayers.Map */ map: null, /** * APIProperty: div * {DOMElement} The element that contains the control, if not present the * control is placed inside the map. */ div: null, /** * APIProperty: type * {Number} Controls can have a 'type'. The type determines the type of * interactions which are possible with them when they are placed in an * . */ type: null, /** * Property: allowSelection * {Boolean} By default, controls do not allow selection, because * it may interfere with map dragging. If this is true, OpenLayers * will not prevent selection of the control. * Default is false. */ allowSelection: false, /** * Property: displayClass * {string} This property is used for CSS related to the drawing of the * Control. */ displayClass: "", /** * APIProperty: title * {string} This property is used for showing a tooltip over the * Control. */ title: "", /** * APIProperty: autoActivate * {Boolean} Activate the control when it is added to a map. Default is * false. */ autoActivate: false, /** * APIProperty: active * {Boolean} The control is active (read-only). Use and * to change control state. */ active: null, /** * Property: handlerOptions * {Object} Used to set non-default properties on the control's handler */ handlerOptions: null, /** * Property: handler * {} null */ handler: null, /** * APIProperty: eventListeners * {Object} If set as an option at construction, the eventListeners * object will be registered with . Object * structure must be a listeners object as shown in the example for * the events.on method. */ eventListeners: null, /** * APIProperty: events * {} Events instance for listeners and triggering * control specific events. * * Register a listener for a particular event with the following syntax: * (code) * control.events.register(type, obj, listener); * (end) * * Listeners will be called with a reference to an event object. The * properties of this event depends on exactly what happened. * * All event objects have at least the following properties: * object - {Object} A reference to control.events.object (a reference * to the control). * element - {DOMElement} A reference to control.events.element (which * will be null unless documented otherwise). * * Supported map event types: * activate - Triggered when activated. * deactivate - Triggered when deactivated. */ events: null, /** * Constructor: OpenLayers.Control * Create an OpenLayers Control. The options passed as a parameter * directly extend the control. For example passing the following: * * > var control = new OpenLayers.Control({div: myDiv}); * * Overrides the default div attribute value of null. * * Parameters: * options - {Object} */ initialize: function (options) { // We do this before the extend so that instances can override // className in options. this.displayClass = this.CLASS_NAME.replace("OpenLayers.", "ol").replace(/\./g, ""); OpenLayers.Util.extend(this, options); this.events = new OpenLayers.Events(this); if(this.eventListeners instanceof Object) { this.events.on(this.eventListeners); } if (this.id == null) { this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_"); } }, /** * Method: destroy * The destroy method is used to perform any clean up before the control * is dereferenced. Typically this is where event listeners are removed * to prevent memory leaks. */ destroy: function () { if(this.events) { if(this.eventListeners) { this.events.un(this.eventListeners); } this.events.destroy(); this.events = null; } this.eventListeners = null; // eliminate circular references if (this.handler) { this.handler.destroy(); this.handler = null; } if(this.handlers) { for(var key in this.handlers) { if(this.handlers.hasOwnProperty(key) && typeof this.handlers[key].destroy == "function") { this.handlers[key].destroy(); } } this.handlers = null; } if (this.map) { this.map.removeControl(this); this.map = null; } this.div = null; }, /** * Method: setMap * Set the map property for the control. This is done through an accessor * so that subclasses can override this and take special action once * they have their map variable set. * * Parameters: * map - {} */ setMap: function(map) { this.map = map; if (this.handler) { this.handler.setMap(map); } }, /** * Method: draw * The draw method is called when the control is ready to be displayed * on the page. If a div has not been created one is created. Controls * with a visual component will almost always want to override this method * to customize the look of control. * * Parameters: * px - {} The top-left pixel position of the control * or null. * * Returns: * {DOMElement} A reference to the DIV DOMElement containing the control */ draw: function (px) { if (this.div == null) { this.div = OpenLayers.Util.createDiv(this.id); this.div.className = this.displayClass; if (!this.allowSelection) { this.div.className += " olControlNoSelect"; this.div.setAttribute("unselectable", "on", 0); this.div.onselectstart = OpenLayers.Function.False; } if (this.title != "") { this.div.title = this.title; } } if (px != null) { this.position = px.clone(); } this.moveTo(this.position); return this.div; }, /** * Method: moveTo * Sets the left and top style attributes to the passed in pixel * coordinates. * * Parameters: * px - {} */ moveTo: function (px) { if ((px != null) && (this.div != null)) { this.div.style.left = px.x + "px"; this.div.style.top = px.y + "px"; } }, /** * APIMethod: activate * Explicitly activates a control and it's associated * handler if one has been set. Controls can be * deactivated by calling the deactivate() method. * * Returns: * {Boolean} True if the control was successfully activated or * false if the control was already active. */ activate: function () { if (this.active) { return false; } if (this.handler) { this.handler.activate(); } this.active = true; if(this.map) { OpenLayers.Element.addClass( this.map.viewPortDiv, this.displayClass.replace(/ /g, "") + "Active" ); } this.events.triggerEvent("activate"); return true; }, /** * APIMethod: deactivate * Deactivates a control and it's associated handler if any. The exact * effect of this depends on the control itself. * * Returns: * {Boolean} True if the control was effectively deactivated or false * if the control was already inactive. */ deactivate: function () { if (this.active) { if (this.handler) { this.handler.deactivate(); } this.active = false; if(this.map) { OpenLayers.Element.removeClass( this.map.viewPortDiv, this.displayClass.replace(/ /g, "") + "Active" ); } this.events.triggerEvent("deactivate"); return true; } return false; }, CLASS_NAME: "OpenLayers.Control" }); /** * Constant: OpenLayers.Control.TYPE_BUTTON */ OpenLayers.Control.TYPE_BUTTON = 1; /** * Constant: OpenLayers.Control.TYPE_TOGGLE */ OpenLayers.Control.TYPE_TOGGLE = 2; /** * Constant: OpenLayers.Control.TYPE_TOOL */ OpenLayers.Control.TYPE_TOOL = 3; /* ====================================================================== OpenLayers/Control/Panel.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Control.js * @requires OpenLayers/Events/buttonclick.js */ /** * Class: OpenLayers.Control.Panel * The Panel control is a container for other controls. With it toolbars * may be composed. * * Inherits from: * - */ OpenLayers.Control.Panel = OpenLayers.Class(OpenLayers.Control, { /** * Property: controls * {Array()} */ controls: null, /** * APIProperty: autoActivate * {Boolean} Activate the control when it is added to a map. Default is * true. */ autoActivate: true, /** * APIProperty: defaultControl * {} The control which is activated when the control is * activated (turned on), which also happens at instantiation. * If is true, will be nullified after the * first activation of the panel. */ defaultControl: null, /** * APIProperty: saveState * {Boolean} If set to true, the active state of this panel's controls will * be stored on panel deactivation, and restored on reactivation. Default * is false. */ saveState: false, /** * APIProperty: allowDepress * {Boolean} If is true the controls can * be deactivated by clicking the icon that represents them. Default * is false. */ allowDepress: false, /** * Property: activeState * {Object} stores the active state of this panel's controls. */ activeState: null, /** * Constructor: OpenLayers.Control.Panel * Create a new control panel. * * Each control in the panel is represented by an icon. When clicking * on an icon, the method is called. * * Specific properties for controls on a panel: * type - {Number} One of , * , . * If not provided, is assumed. * title - {string} Text displayed when mouse is over the icon that * represents the control. * * The of a control determines the behavior when * clicking its icon: * - The control is activated and other * controls of this type in the same panel are deactivated. This is * the default type. * - The active state of the control is * toggled. * - The * method of the control is called, * but its active state is not changed. * * If a control is , it will be drawn with the * olControl[Name]ItemActive class, otherwise with the * olControl[Name]ItemInactive class. * * Parameters: * options - {Object} An optional object whose properties will be used * to extend the control. */ initialize: function(options) { OpenLayers.Control.prototype.initialize.apply(this, [options]); this.controls = []; this.activeState = {}; }, /** * APIMethod: destroy */ destroy: function() { if (this.map) { this.map.events.unregister("buttonclick", this, this.onButtonClick); } OpenLayers.Control.prototype.destroy.apply(this, arguments); for (var ctl, i = this.controls.length - 1; i >= 0; i--) { ctl = this.controls[i]; if (ctl.events) { ctl.events.un({ activate: this.iconOn, deactivate: this.iconOff }); } ctl.panel_div = null; } this.activeState = null; }, /** * APIMethod: activate */ activate: function() { if (OpenLayers.Control.prototype.activate.apply(this, arguments)) { var control; for (var i=0, len=this.controls.length; i=0; i--) { this.div.removeChild(this.div.childNodes[i]); } this.div.innerHTML = ""; if (this.active) { for (var i=0, len=this.controls.length; i} */ activateControl: function (control) { if (!this.active) { return false; } if (control.type == OpenLayers.Control.TYPE_BUTTON) { control.trigger(); return; } if (control.type == OpenLayers.Control.TYPE_TOGGLE) { if (control.active) { control.deactivate(); } else { control.activate(); } return; } if (this.allowDepress && control.active) { control.deactivate(); } else { var c; for (var i=0, len=this.controls.length; i} Controls to add in the panel. */ addControls: function(controls) { if (!(OpenLayers.Util.isArray(controls))) { controls = [controls]; } this.controls = this.controls.concat(controls); for (var i=0, len=controls.length; i} The control to create the HTML * markup for. * * Returns: * {DOMElement} The markup. */ createControlMarkup: function(control) { return document.createElement("div"); }, /** * Method: addControlsToMap * Only for internal use in draw() and addControls() methods. * * Parameters: * controls - {Array()} Controls to add into map. */ addControlsToMap: function (controls) { var control; for (var i=0, len=controls.length; i=0; --i) { if (controls[i].panel_div === button) { this.activateControl(controls[i]); break; } } }, /** * APIMethod: getControlsBy * Get a list of controls with properties matching the given criteria. * * Parameters: * property - {String} A control property to be matched. * match - {String | Object} A string to match. Can also be a regular * expression literal or object. In addition, it can be any object * with a method named test. For reqular expressions or other, if * match.test(control[property]) evaluates to true, the control will be * included in the array returned. If no controls are found, an empty * array is returned. * * Returns: * {Array()} A list of controls matching the given criteria. * An empty array is returned if no matches are found. */ getControlsBy: function(property, match) { var test = (typeof match.test == "function"); var found = OpenLayers.Array.filter(this.controls, function(item) { return item[property] == match || (test && match.test(item[property])); }); return found; }, /** * APIMethod: getControlsByName * Get a list of contorls with names matching the given name. * * Parameters: * match - {String | Object} A control name. The name can also be a regular * expression literal or object. In addition, it can be any object * with a method named test. For reqular expressions or other, if * name.test(control.name) evaluates to true, the control will be included * in the list of controls returned. If no controls are found, an empty * array is returned. * * Returns: * {Array()} A list of controls matching the given name. * An empty array is returned if no matches are found. */ getControlsByName: function(match) { return this.getControlsBy("name", match); }, /** * APIMethod: getControlsByClass * Get a list of controls of a given type (CLASS_NAME). * * Parameters: * match - {String | Object} A control class name. The type can also be a * regular expression literal or object. In addition, it can be any * object with a method named test. For reqular expressions or other, * if type.test(control.CLASS_NAME) evaluates to true, the control will * be included in the list of controls returned. If no controls are * found, an empty array is returned. * * Returns: * {Array()} A list of controls matching the given type. * An empty array is returned if no matches are found. */ getControlsByClass: function(match) { return this.getControlsBy("CLASS_NAME", match); }, CLASS_NAME: "OpenLayers.Control.Panel" }); /* ====================================================================== OpenLayers/Strategy.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/BaseTypes/Class.js */ /** * Class: OpenLayers.Strategy * Abstract vector layer strategy class. Not to be instantiated directly. Use * one of the strategy subclasses instead. */ OpenLayers.Strategy = OpenLayers.Class({ /** * Property: layer * {} The layer this strategy belongs to. */ layer: null, /** * Property: options * {Object} Any options sent to the constructor. */ options: null, /** * Property: active * {Boolean} The control is active. */ active: null, /** * Property: autoActivate * {Boolean} The creator of the strategy can set autoActivate to false * to fully control when the protocol is activated and deactivated. * Defaults to true. */ autoActivate: true, /** * Property: autoDestroy * {Boolean} The creator of the strategy can set autoDestroy to false * to fully control when the strategy is destroyed. Defaults to * true. */ autoDestroy: true, /** * Constructor: OpenLayers.Strategy * Abstract class for vector strategies. Create instances of a subclass. * * Parameters: * options - {Object} Optional object whose properties will be set on the * instance. */ initialize: function(options) { OpenLayers.Util.extend(this, options); this.options = options; // set the active property here, so that user cannot override it this.active = false; }, /** * APIMethod: destroy * Clean up the strategy. */ destroy: function() { this.deactivate(); this.layer = null; this.options = null; }, /** * Method: setLayer * Called to set the property. * * Parameters: * layer - {} */ setLayer: function(layer) { this.layer = layer; }, /** * Method: activate * Activate the strategy. Register any listeners, do appropriate setup. * * Returns: * {Boolean} True if the strategy was successfully activated or false if * the strategy was already active. */ activate: function() { if (!this.active) { this.active = true; return true; } return false; }, /** * Method: deactivate * Deactivate the strategy. Unregister any listeners, do appropriate * tear-down. * * Returns: * {Boolean} True if the strategy was successfully deactivated or false if * the strategy was already inactive. */ deactivate: function() { if (this.active) { this.active = false; return true; } return false; }, CLASS_NAME: "OpenLayers.Strategy" }); /* ====================================================================== OpenLayers/Strategy/Fixed.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Strategy.js */ /** * Class: OpenLayers.Strategy.Fixed * A simple strategy that requests features once and never requests new data. * * Inherits from: * - */ OpenLayers.Strategy.Fixed = OpenLayers.Class(OpenLayers.Strategy, { /** * APIProperty: preload * {Boolean} Load data before layer made visible. Enabling this may result * in considerable overhead if your application loads many data layers * that are not visible by default. Default is false. */ preload: false, /** * Constructor: OpenLayers.Strategy.Fixed * Create a new Fixed strategy. * * Parameters: * options - {Object} Optional object whose properties will be set on the * instance. */ /** * Method: activate * Activate the strategy: load data or add listener to load when visible * * Returns: * {Boolean} True if the strategy was successfully activated or false if * the strategy was already active. */ activate: function() { var activated = OpenLayers.Strategy.prototype.activate.apply(this, arguments); if(activated) { this.layer.events.on({ "refresh": this.load, scope: this }); if(this.layer.visibility == true || this.preload) { this.load(); } else { this.layer.events.on({ "visibilitychanged": this.load, scope: this }); } } return activated; }, /** * Method: deactivate * Deactivate the strategy. Undo what is done in . * * Returns: * {Boolean} The strategy was successfully deactivated. */ deactivate: function() { var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this); if(deactivated) { this.layer.events.un({ "refresh": this.load, "visibilitychanged": this.load, scope: this }); } return deactivated; }, /** * Method: load * Tells protocol to load data and unhooks the visibilitychanged event * * Parameters: * options - {Object} options to pass to protocol read. */ load: function(options) { var layer = this.layer; layer.events.triggerEvent("loadstart", {filter: layer.filter}); layer.protocol.read(OpenLayers.Util.applyDefaults({ callback: this.merge, filter: layer.filter, scope: this }, options)); layer.events.un({ "visibilitychanged": this.load, scope: this }); }, /** * Method: merge * Add all features to the layer. * If the layer projection differs from the map projection, features * will be transformed from the layer projection to the map projection. * * Parameters: * resp - {} The response object passed * by the protocol. */ merge: function(resp) { var layer = this.layer; layer.destroyFeatures(); var features = resp.features; if (features && features.length > 0) { var remote = layer.projection; var local = layer.map.getProjectionObject(); if(!local.equals(remote)) { var geom; for(var i=0, len=features.length; i */ OpenLayers.Control.Zoom = OpenLayers.Class(OpenLayers.Control, { /** * APIProperty: zoomInText * {String} * Text for zoom-in link. Default is "+". */ zoomInText: "+", /** * APIProperty: zoomInId * {String} * Instead of having the control create a zoom in link, you can provide * the identifier for an anchor element already added to the document. * By default, an element with id "olZoomInLink" will be searched for * and used if it exists. */ zoomInId: "olZoomInLink", /** * APIProperty: zoomOutText * {String} * Text for zoom-out link. Default is "\u2212". */ zoomOutText: "\u2212", /** * APIProperty: zoomOutId * {String} * Instead of having the control create a zoom out link, you can provide * the identifier for an anchor element already added to the document. * By default, an element with id "olZoomOutLink" will be searched for * and used if it exists. */ zoomOutId: "olZoomOutLink", /** * Method: draw * * Returns: * {DOMElement} A reference to the DOMElement containing the zoom links. */ draw: function() { var div = OpenLayers.Control.prototype.draw.apply(this), links = this.getOrCreateLinks(div), zoomIn = links.zoomIn, zoomOut = links.zoomOut, eventsInstance = this.map.events; if (zoomOut.parentNode !== div) { eventsInstance = this.events; eventsInstance.attachToElement(zoomOut.parentNode); } eventsInstance.register("buttonclick", this, this.onZoomClick); this.zoomInLink = zoomIn; this.zoomOutLink = zoomOut; return div; }, /** * Method: getOrCreateLinks * * Parameters: * el - {DOMElement} * * Return: * {Object} Object with zoomIn and zoomOut properties referencing links. */ getOrCreateLinks: function(el) { var zoomIn = document.getElementById(this.zoomInId), zoomOut = document.getElementById(this.zoomOutId); if (!zoomIn) { zoomIn = document.createElement("a"); zoomIn.href = "#zoomIn"; zoomIn.appendChild(document.createTextNode(this.zoomInText)); zoomIn.className = "olControlZoomIn"; el.appendChild(zoomIn); } OpenLayers.Element.addClass(zoomIn, "olButton"); if (!zoomOut) { zoomOut = document.createElement("a"); zoomOut.href = "#zoomOut"; zoomOut.appendChild(document.createTextNode(this.zoomOutText)); zoomOut.className = "olControlZoomOut"; el.appendChild(zoomOut); } OpenLayers.Element.addClass(zoomOut, "olButton"); return { zoomIn: zoomIn, zoomOut: zoomOut }; }, /** * Method: onZoomClick * Called when zoomin/out link is clicked. */ onZoomClick: function(evt) { var button = evt.buttonElement; if (button === this.zoomInLink) { this.map.zoomIn(); } else if (button === this.zoomOutLink) { this.map.zoomOut(); } }, /** * Method: destroy * Clean up. */ destroy: function() { if (this.map) { this.map.events.unregister("buttonclick", this, this.onZoomClick); } delete this.zoomInLink; delete this.zoomOutLink; OpenLayers.Control.prototype.destroy.apply(this); }, CLASS_NAME: "OpenLayers.Control.Zoom" }); /* ====================================================================== OpenLayers/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.Polygon = OpenLayers.Class( OpenLayers.Geometry.Collection, { /** * Property: componentTypes * {Array(String)} An array of class names representing the types of * components that the collection can include. A null value means the * component types are not restricted. */ componentTypes: ["OpenLayers.Geometry.LinearRing"], /** * Constructor: OpenLayers.Geometry.Polygon * Constructor for a Polygon geometry. * The first ring (this.component[0])is the outer bounds of the polygon and * all subsequent rings (this.component[1-n]) are internal holes. * * * Parameters: * components - {Array()} */ /** * APIMethod: getArea * Calculated by subtracting the areas of the internal holes from the * area of the outer hole. * * Returns: * {float} The area of the geometry */ getArea: function() { var area = 0.0; if ( this.components && (this.components.length > 0)) { area += Math.abs(this.components[0].getArea()); for (var i=1, len=this.components.length; i} The spatial reference system * for the geometry coordinates. If not provided, Geographic/WGS84 is * assumed. * * Reference: * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion * Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409 * * Returns: * {float} The approximate geodesic area of the polygon in square meters. */ getGeodesicArea: function(projection) { var area = 0.0; if(this.components && (this.components.length > 0)) { area += Math.abs(this.components[0].getGeodesicArea(projection)); for(var i=1, len=this.components.length; i} * * Returns: * {Boolean | Number} The point is inside the polygon. Returns 1 if the * point is on an edge. Returns boolean otherwise. */ containsPoint: function(point) { var numRings = this.components.length; var contained = false; if(numRings > 0) { // check exterior ring - 1 means on edge, boolean otherwise contained = this.components[0].containsPoint(point); if(contained !== 1) { if(contained && numRings > 1) { // check interior rings var hole; for(var i=1; i} Any type of geometry. * * Returns: * {Boolean} The input geometry intersects this one. */ intersects: function(geometry) { var intersect = false; var i, len; if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") { intersect = this.containsPoint(geometry); } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LineString" || geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") { // check if rings/linestrings intersect for(i=0, len=this.components.length; i} The target geometry. * options - {Object} Optional properties for configuring the distance * calculation. * * Valid options: * details - {Boolean} Return details from the distance calculation. * Default is false. * edge - {Boolean} Calculate the distance from this geometry to the * nearest edge of the target geometry. Default is true. If true, * calling distanceTo from a geometry that is wholly contained within * the target will result in a non-zero distance. If false, whenever * geometries intersect, calling distanceTo will return 0. If false, * details cannot be returned. * * Returns: * {Number | Object} The distance between this geometry and the target. * If details is true, the return will be an object with distance, * x0, y0, x1, and y1 properties. The x0 and y0 properties represent * the coordinates of the closest point on this geometry. The x1 and y1 * properties represent the coordinates of the closest point on the * target geometry. */ distanceTo: function(geometry, options) { var edge = !(options && options.edge === false); var result; // this is the case where we might not be looking for distance to edge if(!edge && this.intersects(geometry)) { result = 0; } else { result = OpenLayers.Geometry.Collection.prototype.distanceTo.apply( this, [geometry, options] ); } return result; }, CLASS_NAME: "OpenLayers.Geometry.Polygon" }); /** * APIMethod: createRegularPolygon * Create a regular polygon around a radius. Useful for creating circles * and the like. * * Parameters: * origin - {} center of polygon. * radius - {Float} distance to vertex, in map units. * sides - {Integer} Number of sides. 20 approximates a circle. * rotation - {Float} original angle of rotation, in degrees. */ OpenLayers.Geometry.Polygon.createRegularPolygon = function(origin, radius, sides, rotation) { var angle = Math.PI * ((1/sides) - (1/2)); if(rotation) { angle += (rotation / 180) * Math.PI; } var rotatedAngle, x, y; var points = []; for(var i=0; i * components. Create a new instance with the * constructor. * * Inherits from: * - */ OpenLayers.Geometry.MultiPolygon = OpenLayers.Class( OpenLayers.Geometry.Collection, { /** * Property: componentTypes * {Array(String)} An array of class names representing the types of * components that the collection can include. A null value means the * component types are not restricted. */ componentTypes: ["OpenLayers.Geometry.Polygon"], /** * Constructor: OpenLayers.Geometry.MultiPolygon * Create a new MultiPolygon geometry * * Parameters: * components - {Array()} An array of polygons * used to generate the MultiPolygon * */ CLASS_NAME: "OpenLayers.Geometry.MultiPolygon" }); /* ====================================================================== OpenLayers/Feature.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/BaseTypes/Class.js * @requires OpenLayers/Util.js */ /** * Class: OpenLayers.Feature * Features are combinations of geography and attributes. The OpenLayers.Feature * class specifically combines a marker and a lonlat. */ OpenLayers.Feature = OpenLayers.Class({ /** * Property: layer * {} */ layer: null, /** * Property: id * {String} */ id: null, /** * Property: lonlat * {} */ lonlat: null, /** * Property: data * {Object} */ data: null, /** * Property: marker * {} */ marker: null, /** * APIProperty: popupClass * {} The class which will be used to instantiate * a new Popup. Default is . */ popupClass: null, /** * Property: popup * {} */ popup: null, /** * Constructor: OpenLayers.Feature * Constructor for features. * * Parameters: * layer - {} * lonlat - {} * data - {Object} * * Returns: * {} */ initialize: function(layer, lonlat, data) { this.layer = layer; this.lonlat = lonlat; this.data = (data != null) ? data : {}; this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_"); }, /** * Method: destroy * nullify references to prevent circular references and memory leaks */ destroy: function() { //remove the popup from the map if ((this.layer != null) && (this.layer.map != null)) { if (this.popup != null) { this.layer.map.removePopup(this.popup); } } // remove the marker from the layer if (this.layer != null && this.marker != null) { this.layer.removeMarker(this.marker); } this.layer = null; this.id = null; this.lonlat = null; this.data = null; if (this.marker != null) { this.destroyMarker(this.marker); this.marker = null; } if (this.popup != null) { this.destroyPopup(this.popup); this.popup = null; } }, /** * Method: onScreen * * Returns: * {Boolean} Whether or not the feature is currently visible on screen * (based on its 'lonlat' property) */ onScreen:function() { var onScreen = false; if ((this.layer != null) && (this.layer.map != null)) { var screenBounds = this.layer.map.getExtent(); onScreen = screenBounds.containsLonLat(this.lonlat); } return onScreen; }, /** * Method: createMarker * Based on the data associated with the Feature, create and return a marker object. * * Returns: * {} A Marker Object created from the 'lonlat' and 'icon' properties * set in this.data. If no 'lonlat' is set, returns null. If no * 'icon' is set, OpenLayers.Marker() will load the default image. * * Note - this.marker is set to return value * */ createMarker: function() { if (this.lonlat != null) { this.marker = new OpenLayers.Marker(this.lonlat, this.data.icon); } return this.marker; }, /** * Method: destroyMarker * Destroys marker. * If user overrides the createMarker() function, s/he should be able * to also specify an alternative function for destroying it */ destroyMarker: function() { this.marker.destroy(); }, /** * Method: createPopup * Creates a popup object created from the 'lonlat', 'popupSize', * and 'popupContentHTML' properties set in this.data. It uses * this.marker.icon as default anchor. * * If no 'lonlat' is set, returns null. * If no this.marker has been created, no anchor is sent. * * Note - the returned popup object is 'owned' by the feature, so you * cannot use the popup's destroy method to discard the popup. * Instead, you must use the feature's destroyPopup * * Note - this.popup is set to return value * * Parameters: * closeBox - {Boolean} create popup with closebox or not * * Returns: * {} Returns the created popup, which is also set * as 'popup' property of this feature. Will be of whatever type * specified by this feature's 'popupClass' property, but must be * of type . * */ createPopup: function(closeBox) { if (this.lonlat != null) { if (!this.popup) { var anchor = (this.marker) ? this.marker.icon : null; var popupClass = this.popupClass ? this.popupClass : OpenLayers.Popup.Anchored; this.popup = new popupClass(this.id + "_popup", this.lonlat, this.data.popupSize, this.data.popupContentHTML, anchor, closeBox); } if (this.data.overflow != null) { this.popup.contentDiv.style.overflow = this.data.overflow; } this.popup.feature = this; } return this.popup; }, /** * Method: destroyPopup * Destroys the popup created via createPopup. * * As with the marker, if user overrides the createPopup() function, s/he * should also be able to override the destruction */ destroyPopup: function() { if (this.popup) { this.popup.feature = null; this.popup.destroy(); this.popup = null; } }, CLASS_NAME: "OpenLayers.Feature" }); /* ====================================================================== OpenLayers/Feature/Vector.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ // TRASH THIS OpenLayers.State = { /** states */ UNKNOWN: 'Unknown', INSERT: 'Insert', UPDATE: 'Update', DELETE: 'Delete' }; /** * @requires OpenLayers/Feature.js * @requires OpenLayers/Util.js */ /** * Class: OpenLayers.Feature.Vector * Vector features use the OpenLayers.Geometry classes as geometry description. * They have an 'attributes' property, which is the data object, and a 'style' * property, the default values of which are defined in the * objects. * * Inherits from: * - */ OpenLayers.Feature.Vector = OpenLayers.Class(OpenLayers.Feature, { /** * Property: fid * {String} */ fid: null, /** * APIProperty: geometry * {} */ geometry: null, /** * APIProperty: attributes * {Object} This object holds arbitrary, serializable properties that * describe the feature. */ attributes: null, /** * Property: bounds * {} The box bounding that feature's geometry, that * property can be set by an object when * deserializing the feature, so in most cases it represents an * information set by the server. */ bounds: null, /** * Property: state * {String} */ state: null, /** * APIProperty: style * {Object} */ style: null, /** * APIProperty: url * {String} If this property is set it will be taken into account by * {} when upadting or deleting the feature. */ url: null, /** * Property: renderIntent * {String} rendering intent currently being used */ renderIntent: "default", /** * APIProperty: modified * {Object} An object with the originals of the geometry and attributes of * the feature, if they were changed. Currently this property is only read * by , and written by * , which sets the geometry property. * Applications can set the originals of modified attributes in the * attributes property. Note that applications have to check if this * object and the attributes property is already created before using it. * After a change made with ModifyFeature, this object could look like * * (code) * { * geometry: >Object * } * (end) * * When an application has made changes to feature attributes, it could * have set the attributes to something like this: * * (code) * { * attributes: { * myAttribute: "original" * } * } * (end) * * Note that only checks for truthy values in * *modified.geometry* and the attribute names in *modified.attributes*, * but it is recommended to set the original values (and not just true) as * attribute value, so applications could use this information to undo * changes. */ modified: null, /** * Constructor: OpenLayers.Feature.Vector * Create a vector feature. * * Parameters: * geometry - {} The geometry that this feature * represents. * attributes - {Object} An optional object that will be mapped to the * property. * style - {Object} An optional style object. */ initialize: function(geometry, attributes, style) { OpenLayers.Feature.prototype.initialize.apply(this, [null, null, attributes]); this.lonlat = null; this.geometry = geometry ? geometry : null; this.state = null; this.attributes = {}; if (attributes) { this.attributes = OpenLayers.Util.extend(this.attributes, attributes); } this.style = style ? style : null; }, /** * Method: destroy * nullify references to prevent circular references and memory leaks */ destroy: function() { if (this.layer) { this.layer.removeFeatures(this); this.layer = null; } this.geometry = null; this.modified = null; OpenLayers.Feature.prototype.destroy.apply(this, arguments); }, /** * Method: clone * Create a clone of this vector feature. Does not set any non-standard * properties. * * Returns: * {} An exact clone of this vector feature. */ clone: function () { return new OpenLayers.Feature.Vector( this.geometry ? this.geometry.clone() : null, this.attributes, this.style); }, /** * Method: onScreen * Determine whether the feature is within the map viewport. This method * tests for an intersection between the geometry and the viewport * bounds. If a more effecient but less precise geometry bounds * intersection is desired, call the method with the boundsOnly * parameter true. * * Parameters: * boundsOnly - {Boolean} Only test whether a feature's bounds intersects * the viewport bounds. Default is false. If false, the feature's * geometry must intersect the viewport for onScreen to return true. * * Returns: * {Boolean} The feature is currently visible on screen (optionally * based on its bounds if boundsOnly is true). */ onScreen:function(boundsOnly) { var onScreen = false; if(this.layer && this.layer.map) { var screenBounds = this.layer.map.getExtent(); if(boundsOnly) { var featureBounds = this.geometry.getBounds(); onScreen = screenBounds.intersectsBounds(featureBounds); } else { var screenPoly = screenBounds.toGeometry(); onScreen = screenPoly.intersects(this.geometry); } } return onScreen; }, /** * Method: getVisibility * Determine whether the feature is displayed or not. It may not displayed * because: * - its style display property is set to 'none', * - it doesn't belong to any layer, * - the styleMap creates a symbolizer with display property set to 'none' * for it, * - the layer which it belongs to is not visible. * * Returns: * {Boolean} The feature is currently displayed. */ getVisibility: function() { return !(this.style && this.style.display == 'none' || !this.layer || this.layer && this.layer.styleMap && this.layer.styleMap.createSymbolizer(this, this.renderIntent).display == 'none' || this.layer && !this.layer.getVisibility()); }, /** * Method: createMarker * HACK - we need to decide if all vector features should be able to * create markers * * Returns: * {} For now just returns null */ createMarker: function() { return null; }, /** * Method: destroyMarker * HACK - we need to decide if all vector features should be able to * delete markers * * If user overrides the createMarker() function, s/he should be able * to also specify an alternative function for destroying it */ destroyMarker: function() { // pass }, /** * Method: createPopup * HACK - we need to decide if all vector features should be able to * create popups * * Returns: * {} For now just returns null */ createPopup: function() { return null; }, /** * Method: atPoint * Determins whether the feature intersects with the specified location. * * Parameters: * lonlat - {|Object} OpenLayers.LonLat or an * object with a 'lon' and 'lat' properties. * toleranceLon - {float} Optional tolerance in Geometric Coords * toleranceLat - {float} Optional tolerance in Geographic Coords * * Returns: * {Boolean} Whether or not the feature is at the specified location */ atPoint: function(lonlat, toleranceLon, toleranceLat) { var atPoint = false; if(this.geometry) { atPoint = this.geometry.atPoint(lonlat, toleranceLon, toleranceLat); } return atPoint; }, /** * Method: destroyPopup * HACK - we need to decide if all vector features should be able to * delete popups */ destroyPopup: function() { // pass }, /** * Method: move * Moves the feature and redraws it at its new location * * Parameters: * location - { or } the * location to which to move the feature. */ move: function(location) { if(!this.layer || !this.geometry.move){ //do nothing if no layer or immoveable geometry return undefined; } var pixel; if (location.CLASS_NAME == "OpenLayers.LonLat") { pixel = this.layer.getViewPortPxFromLonLat(location); } else { pixel = location; } var lastPixel = this.layer.getViewPortPxFromLonLat(this.geometry.getBounds().getCenterLonLat()); var res = this.layer.map.getResolution(); this.geometry.move(res * (pixel.x - lastPixel.x), res * (lastPixel.y - pixel.y)); this.layer.drawFeature(this); return lastPixel; }, /** * Method: toState * Sets the new state * * Parameters: * state - {String} */ toState: function(state) { if (state == OpenLayers.State.UPDATE) { switch (this.state) { case OpenLayers.State.UNKNOWN: case OpenLayers.State.DELETE: this.state = state; break; case OpenLayers.State.UPDATE: case OpenLayers.State.INSERT: break; } } else if (state == OpenLayers.State.INSERT) { switch (this.state) { case OpenLayers.State.UNKNOWN: break; default: this.state = state; break; } } else if (state == OpenLayers.State.DELETE) { switch (this.state) { case OpenLayers.State.INSERT: // the feature should be destroyed break; case OpenLayers.State.DELETE: break; case OpenLayers.State.UNKNOWN: case OpenLayers.State.UPDATE: this.state = state; break; } } else if (state == OpenLayers.State.UNKNOWN) { this.state = state; } }, CLASS_NAME: "OpenLayers.Feature.Vector" }); /** * Constant: OpenLayers.Feature.Vector.style * OpenLayers features can have a number of style attributes. The 'default' * style will typically be used if no other style is specified. These * styles correspond for the most part, to the styling properties defined * by the SVG standard. * Information on fill properties: http://www.w3.org/TR/SVG/painting.html#FillProperties * Information on stroke properties: http://www.w3.org/TR/SVG/painting.html#StrokeProperties * * Symbolizer properties: * fill - {Boolean} Set to false if no fill is desired. * fillColor - {String} Hex fill color. Default is "#ee9900". * fillOpacity - {Number} Fill opacity (0-1). Default is 0.4 * stroke - {Boolean} Set to false if no stroke is desired. * strokeColor - {String} Hex stroke color. Default is "#ee9900". * strokeOpacity - {Number} Stroke opacity (0-1). Default is 1. * strokeWidth - {Number} Pixel stroke width. Default is 1. * strokeLinecap - {String} Stroke cap type. Default is "round". [butt | round | square] * strokeDashstyle - {String} Stroke dash style. Default is "solid". [dot | dash | dashdot | longdash | longdashdot | solid] * graphic - {Boolean} Set to false if no graphic is desired. * pointRadius - {Number} Pixel point radius. Default is 6. * pointerEvents - {String} Default is "visiblePainted". * cursor - {String} Default is "". * externalGraphic - {String} Url to an external graphic that will be used for rendering points. * graphicWidth - {Number} Pixel width for sizing an external graphic. * graphicHeight - {Number} Pixel height for sizing an external graphic. * graphicOpacity - {Number} Opacity (0-1) for an external graphic. * graphicXOffset - {Number} Pixel offset along the positive x axis for displacing an external graphic. * graphicYOffset - {Number} Pixel offset along the positive y axis for displacing an external graphic. * rotation - {Number} For point symbolizers, this is the rotation of a graphic in the clockwise direction about its center point (or any point off center as specified by graphicXOffset and graphicYOffset). * graphicZIndex - {Number} The integer z-index value to use in rendering. * graphicName - {String} Named graphic to use when rendering points. Supported values include "circle" (default), * "square", "star", "x", "cross", "triangle". * graphicTitle - {String} Tooltip when hovering over a feature. *deprecated*, use title instead * title - {String} Tooltip when hovering over a feature. Not supported by the canvas renderer. * backgroundGraphic - {String} Url to a graphic to be used as the background under an externalGraphic. * backgroundGraphicZIndex - {Number} The integer z-index value to use in rendering the background graphic. * backgroundXOffset - {Number} The x offset (in pixels) for the background graphic. * backgroundYOffset - {Number} The y offset (in pixels) for the background graphic. * backgroundHeight - {Number} The height of the background graphic. If not provided, the graphicHeight will be used. * backgroundWidth - {Number} The width of the background width. If not provided, the graphicWidth will be used. * label - {String} The text for an optional label. For browsers that use the canvas renderer, this requires either * fillText or mozDrawText to be available. * labelAlign - {String} Label alignment. This specifies the insertion point relative to the text. It is a string * composed of two characters. The first character is for the horizontal alignment, the second for the vertical * alignment. Valid values for horizontal alignment: "l"=left, "c"=center, "r"=right. Valid values for vertical * alignment: "t"=top, "m"=middle, "b"=bottom. Example values: "lt", "cm", "rb". Default is "cm". * labelXOffset - {Number} Pixel offset along the positive x axis for displacing the label. Not supported by the canvas renderer. * labelYOffset - {Number} Pixel offset along the positive y axis for displacing the label. Not supported by the canvas renderer. * labelSelect - {Boolean} If set to true, labels will be selectable using SelectFeature or similar controls. * Default is false. * labelOutlineColor - {String} The color of the label outline. Default is 'white'. Only supported by the canvas & SVG renderers. * labelOutlineWidth - {Number} The width of the label outline. Default is 3, set to 0 or null to disable. Only supported by the SVG renderers. * labelOutlineOpacity - {Number} The opacity (0-1) of the label outline. Default is fontOpacity. Only supported by the canvas & SVG renderers. * fontColor - {String} The font color for the label, to be provided like CSS. * fontOpacity - {Number} Opacity (0-1) for the label * fontFamily - {String} The font family for the label, to be provided like in CSS. * fontSize - {String} The font size for the label, to be provided like in CSS. * fontStyle - {String} The font style for the label, to be provided like in CSS. * fontWeight - {String} The font weight for the label, to be provided like in CSS. * display - {String} Symbolizers will have no effect if display is set to "none". All other values have no effect. */ OpenLayers.Feature.Vector.style = { 'default': { fillColor: "#ee9900", fillOpacity: 0.4, hoverFillColor: "white", hoverFillOpacity: 0.8, strokeColor: "#ee9900", strokeOpacity: 1, strokeWidth: 1, strokeLinecap: "round", strokeDashstyle: "solid", hoverStrokeColor: "red", hoverStrokeOpacity: 1, hoverStrokeWidth: 0.2, pointRadius: 6, hoverPointRadius: 1, hoverPointUnit: "%", pointerEvents: "visiblePainted", cursor: "inherit", fontColor: "#000000", labelAlign: "cm", labelOutlineColor: "white", labelOutlineWidth: 3 }, 'select': { fillColor: "blue", fillOpacity: 0.4, hoverFillColor: "white", hoverFillOpacity: 0.8, strokeColor: "blue", strokeOpacity: 1, strokeWidth: 2, strokeLinecap: "round", strokeDashstyle: "solid", hoverStrokeColor: "red", hoverStrokeOpacity: 1, hoverStrokeWidth: 0.2, pointRadius: 6, hoverPointRadius: 1, hoverPointUnit: "%", pointerEvents: "visiblePainted", cursor: "pointer", fontColor: "#000000", labelAlign: "cm", labelOutlineColor: "white", labelOutlineWidth: 3 }, 'temporary': { fillColor: "#66cccc", fillOpacity: 0.2, hoverFillColor: "white", hoverFillOpacity: 0.8, strokeColor: "#66cccc", strokeOpacity: 1, strokeLinecap: "round", strokeWidth: 2, strokeDashstyle: "solid", hoverStrokeColor: "red", hoverStrokeOpacity: 1, hoverStrokeWidth: 0.2, pointRadius: 6, hoverPointRadius: 1, hoverPointUnit: "%", pointerEvents: "visiblePainted", cursor: "inherit", fontColor: "#000000", labelAlign: "cm", labelOutlineColor: "white", labelOutlineWidth: 3 }, 'delete': { display: "none" } }; /* ====================================================================== OpenLayers/Style.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/BaseTypes/Class.js * @requires OpenLayers/Util.js * @requires OpenLayers/Feature/Vector.js */ /** * Class: OpenLayers.Style * This class represents a UserStyle obtained * from a SLD, containing styling rules. */ OpenLayers.Style = OpenLayers.Class({ /** * Property: id * {String} A unique id for this session. */ id: null, /** * APIProperty: name * {String} */ name: null, /** * Property: title * {String} Title of this style (set if included in SLD) */ title: null, /** * Property: description * {String} Description of this style (set if abstract is included in SLD) */ description: null, /** * APIProperty: layerName * {} name of the layer that this style belongs to, usually * according to the NamedLayer attribute of an SLD document. */ layerName: null, /** * APIProperty: isDefault * {Boolean} */ isDefault: false, /** * Property: rules * {Array()} */ rules: null, /** * APIProperty: context * {Object} An optional object with properties that symbolizers' property * values should be evaluated against. If no context is specified, * feature.attributes will be used */ context: null, /** * Property: defaultStyle * {Object} hash of style properties to use as default for merging * rule-based style symbolizers onto. If no rules are defined, * createSymbolizer will return this style. If is set to * true, the defaultStyle will only be taken into account if there are * rules defined. */ defaultStyle: null, /** * Property: defaultsPerSymbolizer * {Boolean} If set to true, the will extend the symbolizer * of every rule. Properties of the will also be used to set * missing symbolizer properties if the symbolizer has stroke, fill or * graphic set to true. Default is false. */ defaultsPerSymbolizer: false, /** * Property: propertyStyles * {Hash of Boolean} cache of style properties that need to be parsed for * propertyNames. Property names are keys, values won't be used. */ propertyStyles: null, /** * Constructor: OpenLayers.Style * Creates a UserStyle. * * Parameters: * style - {Object} Optional hash of style properties that will be * used as default style for this style object. This style * applies if no rules are specified. Symbolizers defined in * rules will extend this default style. * options - {Object} An optional object with properties to set on the * style. * * Valid options: * rules - {Array()} List of rules to be added to the * style. * * Returns: * {} */ initialize: function(style, options) { OpenLayers.Util.extend(this, options); this.rules = []; if(options && options.rules) { this.addRules(options.rules); } // use the default style from OpenLayers.Feature.Vector if no style // was given in the constructor this.setDefaultStyle(style || OpenLayers.Feature.Vector.style["default"]); this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_"); }, /** * APIMethod: destroy * nullify references to prevent circular references and memory leaks */ destroy: function() { for (var i=0, len=this.rules.length; i} feature to evaluate rules for * * Returns: * {Object} symbolizer hash */ createSymbolizer: function(feature) { var style = this.defaultsPerSymbolizer ? {} : this.createLiterals( OpenLayers.Util.extend({}, this.defaultStyle), feature); var rules = this.rules; var rule, context; var elseRules = []; var appliedRules = false; for(var i=0, len=rules.length; i 0) { appliedRules = true; for(var i=0, len=elseRules.length; i 0 && appliedRules == false) { style.display = "none"; } if (style.label != null && typeof style.label !== "string") { style.label = String(style.label); } return style; }, /** * Method: applySymbolizer * * Parameters: * rule - {} * style - {Object} * feature - {} * * Returns: * {Object} A style with new symbolizer applied. */ applySymbolizer: function(rule, style, feature) { var symbolizerPrefix = feature.geometry ? this.getSymbolizerPrefix(feature.geometry) : OpenLayers.Style.SYMBOLIZER_PREFIXES[0]; var symbolizer = rule.symbolizer[symbolizerPrefix] || rule.symbolizer; if(this.defaultsPerSymbolizer === true) { var defaults = this.defaultStyle; OpenLayers.Util.applyDefaults(symbolizer, { pointRadius: defaults.pointRadius }); if(symbolizer.stroke === true || symbolizer.graphic === true) { OpenLayers.Util.applyDefaults(symbolizer, { strokeWidth: defaults.strokeWidth, strokeColor: defaults.strokeColor, strokeOpacity: defaults.strokeOpacity, strokeDashstyle: defaults.strokeDashstyle, strokeLinecap: defaults.strokeLinecap }); } if(symbolizer.fill === true || symbolizer.graphic === true) { OpenLayers.Util.applyDefaults(symbolizer, { fillColor: defaults.fillColor, fillOpacity: defaults.fillOpacity }); } if(symbolizer.graphic === true) { OpenLayers.Util.applyDefaults(symbolizer, { pointRadius: this.defaultStyle.pointRadius, externalGraphic: this.defaultStyle.externalGraphic, graphicName: this.defaultStyle.graphicName, graphicOpacity: this.defaultStyle.graphicOpacity, graphicWidth: this.defaultStyle.graphicWidth, graphicHeight: this.defaultStyle.graphicHeight, graphicXOffset: this.defaultStyle.graphicXOffset, graphicYOffset: this.defaultStyle.graphicYOffset }); } } // merge the style with the current style return this.createLiterals( OpenLayers.Util.extend(style, symbolizer), feature); }, /** * Method: createLiterals * creates literals for all style properties that have an entry in * . * * Parameters: * style - {Object} style to create literals for. Will be modified * inline. * feature - {Object} * * Returns: * {Object} the modified style */ createLiterals: function(style, feature) { var context = OpenLayers.Util.extend({}, feature.attributes || feature.data); OpenLayers.Util.extend(context, this.context); for (var i in this.propertyStyles) { style[i] = OpenLayers.Style.createLiteral(style[i], context, feature, i); } return style; }, /** * Method: findPropertyStyles * Looks into all rules for this style and the defaultStyle to collect * all the style hash property names containing ${...} strings that have * to be replaced using the createLiteral method before returning them. * * Returns: * {Object} hash of property names that need createLiteral parsing. The * name of the property is the key, and the value is true; */ findPropertyStyles: function() { var propertyStyles = {}; // check the default style var style = this.defaultStyle; this.addPropertyStyles(propertyStyles, style); // walk through all rules to check for properties in their symbolizer var rules = this.rules; var symbolizer, value; for (var i=0, len=rules.length; i)} */ addRules: function(rules) { Array.prototype.push.apply(this.rules, rules); this.propertyStyles = this.findPropertyStyles(); }, /** * APIMethod: setDefaultStyle * Sets the default style for this style object. * * Parameters: * style - {Object} Hash of style properties */ setDefaultStyle: function(style) { this.defaultStyle = style; this.propertyStyles = this.findPropertyStyles(); }, /** * Method: getSymbolizerPrefix * Returns the correct symbolizer prefix according to the * geometry type of the passed geometry * * Parameters: * geometry - {} * * Returns: * {String} key of the according symbolizer */ getSymbolizerPrefix: function(geometry) { var prefixes = OpenLayers.Style.SYMBOLIZER_PREFIXES; for (var i=0, len=prefixes.length; i} Clone of this style. */ clone: function() { var options = OpenLayers.Util.extend({}, this); // clone rules if(this.rules) { options.rules = []; for(var i=0, len=this.rules.length; i} optional feature to pass to * for evaluating functions in the * context. * property - {String} optional, name of the property for which the literal is * being created for evaluating functions in the context. * * Returns: * {String} the parsed value. In the example of the value parameter above, the * result would be "foo valueOfBar", assuming that the passed feature has an * attribute named "bar" with the value "valueOfBar". */ OpenLayers.Style.createLiteral = function(value, context, feature, property) { if (typeof value == "string" && value.indexOf("${") != -1) { value = OpenLayers.String.format(value, context, [feature, property]); value = (isNaN(value) || !value) ? value : parseFloat(value); } return value; }; /** * Constant: OpenLayers.Style.SYMBOLIZER_PREFIXES * {Array} prefixes of the sld symbolizers. These are the * same as the main geometry types */ OpenLayers.Style.SYMBOLIZER_PREFIXES = ['Point', 'Line', 'Polygon', 'Text', 'Raster']; /* ====================================================================== OpenLayers/Filter.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/BaseTypes/Class.js * @requires OpenLayers/Util.js * @requires OpenLayers/Style.js */ /** * Class: OpenLayers.Filter * This class represents an OGC Filter. */ OpenLayers.Filter = OpenLayers.Class({ /** * Constructor: OpenLayers.Filter * This class represents a generic filter. * * Parameters: * options - {Object} Optional object whose properties will be set on the * instance. * * Returns: * {} */ initialize: function(options) { OpenLayers.Util.extend(this, options); }, /** * APIMethod: destroy * Remove reference to anything added. */ destroy: function() { }, /** * APIMethod: evaluate * Evaluates this filter in a specific context. Instances or subclasses * are supposed to override this method. * * Parameters: * context - {Object} Context to use in evaluating the filter. If a vector * feature is provided, the feature.attributes will be used as context. * * Returns: * {Boolean} The filter applies. */ evaluate: function(context) { return true; }, /** * APIMethod: clone * Clones this filter. Should be implemented by subclasses. * * Returns: * {} Clone of this filter. */ clone: function() { return null; }, /** * APIMethod: toString * * Returns: * {String} Include in your build to get a CQL * representation of the filter returned. Otherwise "[Object object]" * will be returned. */ toString: function() { var string; if (OpenLayers.Format && OpenLayers.Format.CQL) { string = OpenLayers.Format.CQL.prototype.write(this); } else { string = Object.prototype.toString.call(this); } return string; }, CLASS_NAME: "OpenLayers.Filter" }); /* ====================================================================== OpenLayers/Filter/Spatial.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Filter.js */ /** * Class: OpenLayers.Filter.Spatial * This class represents a spatial filter. * Currently implemented: BBOX, DWithin and Intersects * * Inherits from: * - */ OpenLayers.Filter.Spatial = OpenLayers.Class(OpenLayers.Filter, { /** * APIProperty: type * {String} Type of spatial filter. * * The type should be one of: * - OpenLayers.Filter.Spatial.BBOX * - OpenLayers.Filter.Spatial.INTERSECTS * - OpenLayers.Filter.Spatial.DWITHIN * - OpenLayers.Filter.Spatial.WITHIN * - OpenLayers.Filter.Spatial.CONTAINS */ type: null, /** * APIProperty: property * {String} Name of the context property to compare. */ property: null, /** * APIProperty: value * { || } The bounds or geometry * to be used by the filter. Use bounds for BBOX filters and geometry * for INTERSECTS or DWITHIN filters. */ value: null, /** * APIProperty: distance * {Number} The distance to use in a DWithin spatial filter. */ distance: null, /** * APIProperty: distanceUnits * {String} The units to use for the distance, e.g. 'm'. */ distanceUnits: null, /** * Constructor: OpenLayers.Filter.Spatial * Creates a spatial filter. * * Parameters: * options - {Object} An optional object with properties to set on the * filter. * * Returns: * {} */ /** * Method: evaluate * Evaluates this filter for a specific feature. * * Parameters: * feature - {} feature to apply the filter to. * * Returns: * {Boolean} The feature meets filter criteria. */ evaluate: function(feature) { var intersect = false; switch(this.type) { case OpenLayers.Filter.Spatial.BBOX: case OpenLayers.Filter.Spatial.INTERSECTS: if(feature.geometry) { var geom = this.value; if(this.value.CLASS_NAME == "OpenLayers.Bounds") { geom = this.value.toGeometry(); } if(feature.geometry.intersects(geom)) { intersect = true; } } break; default: throw new Error('evaluate is not implemented for this filter type.'); } return intersect; }, /** * APIMethod: clone * Clones this filter. * * Returns: * {} Clone of this filter. */ clone: function() { var options = OpenLayers.Util.applyDefaults({ value: this.value && this.value.clone && this.value.clone() }, this); return new OpenLayers.Filter.Spatial(options); }, CLASS_NAME: "OpenLayers.Filter.Spatial" }); OpenLayers.Filter.Spatial.BBOX = "BBOX"; OpenLayers.Filter.Spatial.INTERSECTS = "INTERSECTS"; OpenLayers.Filter.Spatial.DWITHIN = "DWITHIN"; OpenLayers.Filter.Spatial.WITHIN = "WITHIN"; OpenLayers.Filter.Spatial.CONTAINS = "CONTAINS"; /* ====================================================================== OpenLayers/Strategy/BBOX.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of 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 * @requires OpenLayers/Filter/Spatial.js */ /** * Class: OpenLayers.Strategy.BBOX * A simple strategy that reads new features when the viewport invalidates * some bounds. * * Inherits from: * - */ OpenLayers.Strategy.BBOX = OpenLayers.Class(OpenLayers.Strategy, { /** * Property: bounds * {} The current data bounds (in the same projection * as the layer - not always the same projection as the map). */ bounds: null, /** * Property: resolution * {Float} The current data resolution. */ resolution: null, /** * APIProperty: ratio * {Float} The ratio of the data bounds to the viewport bounds (in each * dimension). Default is 2. */ ratio: 2, /** * Property: resFactor * {Float} Optional factor used to determine when previously requested * features are invalid. If set, the resFactor will be compared to the * resolution of the previous request to the current map resolution. * If resFactor > (old / new) and 1/resFactor < (old / new). If you * set a resFactor of 1, data will be requested every time the * resolution changes. If you set a resFactor of 3, data will be * requested if the old resolution is 3 times the new, or if the new is * 3 times the old. If the old bounds do not contain the new bounds * new data will always be requested (with or without considering * resFactor). */ resFactor: null, /** * Property: response * {} The protocol response object returned * by the layer protocol. */ response: null, /** * Constructor: OpenLayers.Strategy.BBOX * Create a new BBOX strategy. * * Parameters: * options - {Object} Optional object whose properties will be set on the * instance. */ /** * Method: activate * Set up strategy with regard to reading new batches of remote data. * * Returns: * {Boolean} The strategy was successfully activated. */ activate: function() { var activated = OpenLayers.Strategy.prototype.activate.call(this); if(activated) { this.layer.events.on({ "moveend": this.update, "refresh": this.update, "visibilitychanged": this.update, scope: this }); this.update(); } return activated; }, /** * Method: deactivate * Tear down strategy with regard to reading new batches of remote data. * * Returns: * {Boolean} The strategy was successfully deactivated. */ deactivate: function() { var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this); if(deactivated) { this.layer.events.un({ "moveend": this.update, "refresh": this.update, "visibilitychanged": this.update, scope: this }); } return deactivated; }, /** * Method: update * Callback function called on "moveend" or "refresh" layer events. * * Parameters: * options - {Object} Optional object whose properties will determine * the behaviour of this Strategy * * Valid options include: * force - {Boolean} if true, new data must be unconditionally read. * noAbort - {Boolean} if true, do not abort previous requests. */ update: function(options) { var mapBounds = this.getMapBounds(); if (mapBounds !== null && ((options && options.force) || (this.layer.visibility && this.layer.calculateInRange() && this.invalidBounds(mapBounds)))) { this.calculateBounds(mapBounds); this.resolution = this.layer.map.getResolution(); this.triggerRead(options); } }, /** * Method: getMapBounds * Get the map bounds expressed in the same projection as this layer. * * Returns: * {} Map bounds in the projection of the layer. */ getMapBounds: function() { if (this.layer.map === null) { return null; } var bounds = this.layer.map.getExtent(); if(bounds && !this.layer.projection.equals( this.layer.map.getProjectionObject())) { bounds = bounds.clone().transform( this.layer.map.getProjectionObject(), this.layer.projection ); } return bounds; }, /** * Method: invalidBounds * Determine whether the previously requested set of features is invalid. * This occurs when the new map bounds do not contain the previously * requested bounds. In addition, if is set, it will be * considered. * * Parameters: * mapBounds - {} the current map extent, will be * retrieved from the map object if not provided * * Returns: * {Boolean} */ invalidBounds: function(mapBounds) { if(!mapBounds) { mapBounds = this.getMapBounds(); } var invalid = !this.bounds || !this.bounds.containsBounds(mapBounds); if(!invalid && this.resFactor) { var ratio = this.resolution / this.layer.map.getResolution(); invalid = (ratio >= this.resFactor || ratio <= (1 / this.resFactor)); } return invalid; }, /** * Method: calculateBounds * * Parameters: * mapBounds - {} the current map extent, will be * retrieved from the map object if not provided */ calculateBounds: function(mapBounds) { if(!mapBounds) { mapBounds = this.getMapBounds(); } var center = mapBounds.getCenterLonLat(); var dataWidth = mapBounds.getWidth() * this.ratio; var dataHeight = mapBounds.getHeight() * this.ratio; this.bounds = new OpenLayers.Bounds( center.lon - (dataWidth / 2), center.lat - (dataHeight / 2), center.lon + (dataWidth / 2), center.lat + (dataHeight / 2) ); }, /** * Method: triggerRead * * Parameters: * options - {Object} Additional options for the protocol's read method * (optional) * * Returns: * {} The protocol response object * returned by the layer protocol. */ triggerRead: function(options) { if (this.response && !(options && options.noAbort === true)) { this.layer.protocol.abort(this.response); this.layer.events.triggerEvent("loadend"); } var evt = {filter: this.createFilter()}; this.layer.events.triggerEvent("loadstart", evt); this.response = this.layer.protocol.read( OpenLayers.Util.applyDefaults({ filter: evt.filter, callback: this.merge, scope: this }, options)); }, /** * Method: createFilter * Creates a spatial BBOX filter. If the layer that this strategy belongs * to has a filter property, this filter will be combined with the BBOX * filter. * * Returns * {} The filter object. */ createFilter: function() { var filter = new OpenLayers.Filter.Spatial({ type: OpenLayers.Filter.Spatial.BBOX, value: this.bounds, projection: this.layer.projection }); if (this.layer.filter) { filter = new OpenLayers.Filter.Logical({ type: OpenLayers.Filter.Logical.AND, filters: [this.layer.filter, filter] }); } return filter; }, /** * Method: merge * Given a list of features, determine which ones to add to the layer. * If the layer projection differs from the map projection, features * will be transformed from the layer projection to the map projection. * * Parameters: * resp - {} The response object passed * by the protocol. */ merge: function(resp) { this.layer.destroyFeatures(); if (resp.success()) { var features = resp.features; if(features && features.length > 0) { var remote = this.layer.projection; var local = this.layer.map.getProjectionObject(); if(!local.equals(remote)) { var geom; for(var i=0, len=features.length; i */ 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 * {} The last feature that was hovered. */ feature: null, /** * Property: lastFeature * {} The last feature that was handled. */ lastFeature: null, /** * Property: down * {} The location of the last mousedown. */ down: null, /** * Property: up * {} 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 - {} * layer - {} * 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 - {} * * 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 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 {}, keyed by names of well known * rendering intents (e.g. "default", "temporary", "select", "delete"). */ styles: null, /** * Property: extendDefault * {Boolean} if true, every render intent will extend the symbolizers * specified for the "default" intent at rendering time. Otherwise, every * rendering intent will be treated as a completely independent style. */ extendDefault: true, /** * Constructor: OpenLayers.StyleMap * * Parameters: * style - {Object} Optional. Either a style hash, or a style object, or * a hash of style objects (style hashes) keyed by rendering * intent. If just one style hash or style object is passed, * this will be used for all known render intents (default, * select, temporary) * options - {Object} optional hash of additional options for this * instance */ initialize: function (style, options) { this.styles = { "default": new OpenLayers.Style( OpenLayers.Feature.Vector.style["default"]), "select": new OpenLayers.Style( OpenLayers.Feature.Vector.style["select"]), "temporary": new OpenLayers.Style( OpenLayers.Feature.Vector.style["temporary"]), "delete": new OpenLayers.Style( OpenLayers.Feature.Vector.style["delete"]) }; // take whatever the user passed as style parameter and convert it // into parts of stylemap. if(style instanceof OpenLayers.Style) { // user passed a style object this.styles["default"] = style; this.styles["select"] = style; this.styles["temporary"] = style; this.styles["delete"] = style; } else if(typeof style == "object") { for(var key in style) { if(style[key] instanceof OpenLayers.Style) { // user passed a hash of style objects this.styles[key] = style[key]; } else if(typeof style[key] == "object") { // user passsed a hash of style hashes this.styles[key] = new OpenLayers.Style(style[key]); } else { // user passed a style hash (i.e. symbolizer) this.styles["default"] = new OpenLayers.Style(style); this.styles["select"] = new OpenLayers.Style(style); this.styles["temporary"] = new OpenLayers.Style(style); this.styles["delete"] = new OpenLayers.Style(style); break; } } } OpenLayers.Util.extend(this, options); }, /** * Method: destroy */ destroy: function() { for(var key in this.styles) { this.styles[key].destroy(); } this.styles = null; }, /** * Method: createSymbolizer * Creates the symbolizer for a feature for a render intent. * * Parameters: * feature - {} The feature to evaluate the rules * of the intended style against. * intent - {String} The intent determines the symbolizer that will be * used to draw the feature. Well known intents are "default" * (for just drawing the features), "select" (for selected * features) and "temporary" (for drawing features). * * Returns: * {Object} symbolizer hash */ createSymbolizer: function(feature, intent) { if(!feature) { feature = new OpenLayers.Feature.Vector(); } if(!this.styles[intent]) { intent = "default"; } feature.renderIntent = intent; var defaultSymbolizer = {}; if(this.extendDefault && intent != "default") { defaultSymbolizer = this.styles["default"].createSymbolizer(feature); } return OpenLayers.Util.extend(defaultSymbolizer, this.styles[intent].createSymbolizer(feature)); }, /** * Method: addUniqueValueRules * Convenience method to create comparison rules for unique values of a * property. The rules will be added to the style object for a specified * rendering intent. This method is a shortcut for creating something like * the "unique value legends" familiar from well known desktop GIS systems * * Parameters: * renderIntent - {String} rendering intent to add the rules to * property - {String} values of feature attributes to create the * rules for * symbolizers - {Object} Hash of symbolizers, keyed by the desired * property values * context - {Object} An optional object with properties that * symbolizers' property values should be evaluated * against. If no context is specified, feature.attributes * will be used */ addUniqueValueRules: function(renderIntent, property, symbolizers, context) { var rules = []; for (var value in symbolizers) { rules.push(new OpenLayers.Rule({ symbolizer: symbolizers[value], context: context, filter: new OpenLayers.Filter.Comparison({ type: OpenLayers.Filter.Comparison.EQUAL_TO, property: property, value: value }) })); } this.styles[renderIntent].addRules(rules); }, CLASS_NAME: "OpenLayers.StyleMap" }); /* ====================================================================== OpenLayers/Layer/Vector.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Layer.js * @requires OpenLayers/Renderer.js * @requires OpenLayers/StyleMap.js * @requires OpenLayers/Feature/Vector.js * @requires OpenLayers/Console.js * @requires OpenLayers/Lang.js */ /** * Class: OpenLayers.Layer.Vector * Instances of OpenLayers.Layer.Vector are used to render vector data from * a variety of sources. Create a new vector layer with the * constructor. * * Inherits from: * - */ OpenLayers.Layer.Vector = OpenLayers.Class(OpenLayers.Layer, { /** * APIProperty: events * {} * * Register a listener for a particular event with the following syntax: * (code) * layer.events.register(type, obj, listener); * (end) * * Listeners will be called with a reference to an event object. The * properties of this event depends on exactly what happened. * * All event objects have at least the following properties: * object - {Object} A reference to layer.events.object. * element - {DOMElement} A reference to layer.events.element. * * Supported map event types (in addition to those from ): * beforefeatureadded - Triggered before a feature is added. Listeners * will receive an object with a *feature* property referencing the * feature to be added. To stop the feature from being added, a * listener should return false. * beforefeaturesadded - Triggered before an array of features is added. * Listeners will receive an object with a *features* property * referencing the feature to be added. To stop the features from * being added, a listener should return false. * featureadded - Triggered after a feature is added. The event * object passed to listeners will have a *feature* property with a * reference to the added feature. * featuresadded - Triggered after features are added. The event * object passed to listeners will have a *features* property with a * reference to an array of added features. * beforefeatureremoved - Triggered before a feature is removed. Listeners * will receive an object with a *feature* property referencing the * feature to be removed. * beforefeaturesremoved - Triggered before multiple features are removed. * Listeners will receive an object with a *features* property * referencing the features to be removed. * featureremoved - Triggerd after a feature is removed. The event * object passed to listeners will have a *feature* property with a * reference to the removed feature. * featuresremoved - Triggered after features are removed. The event * object passed to listeners will have a *features* property with a * reference to an array of removed features. * beforefeatureselected - Triggered before a feature is selected. Listeners * will receive an object with a *feature* property referencing the * feature to be selected. To stop the feature from being selectd, a * listener should return false. * featureselected - Triggered after a feature is selected. Listeners * will receive an object with a *feature* property referencing the * selected feature. * featureunselected - Triggered after a feature is unselected. * Listeners will receive an object with a *feature* property * referencing the unselected feature. * beforefeaturemodified - Triggered when a feature is selected to * be modified. Listeners will receive an object with a *feature* * property referencing the selected feature. * featuremodified - Triggered when a feature has been modified. * Listeners will receive an object with a *feature* property referencing * the modified feature. * afterfeaturemodified - Triggered when a feature is finished being modified. * Listeners will receive an object with a *feature* property referencing * the modified feature. * vertexmodified - Triggered when a vertex within any feature geometry * has been modified. Listeners will receive an object with a * *feature* property referencing the modified feature, a *vertex* * property referencing the vertex modified (always a point geometry), * and a *pixel* property referencing the pixel location of the * modification. * vertexremoved - Triggered when a vertex within any feature geometry * has been deleted. Listeners will receive an object with a * *feature* property referencing the modified feature, a *vertex* * property referencing the vertex modified (always a point geometry), * and a *pixel* property referencing the pixel location of the * removal. * sketchstarted - Triggered when a feature sketch bound for this layer * is started. Listeners will receive an object with a *feature* * property referencing the new sketch feature and a *vertex* property * referencing the creation point. * sketchmodified - Triggered when a feature sketch bound for this layer * is modified. Listeners will receive an object with a *vertex* * property referencing the modified vertex and a *feature* property * referencing the sketch feature. * sketchcomplete - Triggered when a feature sketch bound for this layer * is complete. Listeners will receive an object with a *feature* * property referencing the sketch feature. By returning false, a * listener can stop the sketch feature from being added to the layer. * refresh - Triggered when something wants a strategy to ask the protocol * for a new set of features. */ /** * APIProperty: isBaseLayer * {Boolean} The layer is a base layer. Default is false. Set this property * in the layer options. */ isBaseLayer: false, /** * APIProperty: isFixed * {Boolean} Whether the layer remains in one place while dragging the * map. Note that setting this to true will move the layer to the bottom * of the layer stack. */ isFixed: false, /** * APIProperty: features * {Array()} */ features: null, /** * Property: filter * {} The filter set in this layer, * a strategy launching read requests can combined * this filter with its own filter. */ filter: null, /** * Property: selectedFeatures * {Array()} */ selectedFeatures: null, /** * Property: unrenderedFeatures * {Object} hash of features, keyed by feature.id, that the renderer * failed to draw */ unrenderedFeatures: null, /** * APIProperty: reportError * {Boolean} report friendly error message when loading of renderer * fails. */ reportError: true, /** * APIProperty: style * {Object} Default style for the layer */ style: null, /** * Property: styleMap * {} */ styleMap: null, /** * Property: strategies * {Array(})} Optional list of strategies for the layer. */ strategies: null, /** * Property: protocol * {} Optional protocol for the layer. */ protocol: null, /** * Property: renderers * {Array(String)} List of supported Renderer classes. Add to this list to * add support for additional renderers. This list is ordered: * the first renderer which returns true for the 'supported()' * method will be used, if not defined in the 'renderer' option. */ renderers: ['SVG', 'VML', 'Canvas'], /** * Property: renderer * {} */ renderer: null, /** * APIProperty: rendererOptions * {Object} Options for the renderer. See {} for * supported options. */ rendererOptions: null, /** * APIProperty: geometryType * {String} geometryType allows you to limit the types of geometries this * layer supports. This should be set to something like * "OpenLayers.Geometry.Point" to limit types. */ geometryType: null, /** * Property: drawn * {Boolean} Whether the Vector Layer features have been drawn yet. */ drawn: false, /** * APIProperty: ratio * {Float} This specifies the ratio of the size of the visiblity of the Vector Layer features to the size of the map. */ ratio: 1, /** * Constructor: OpenLayers.Layer.Vector * Create a new vector layer * * Parameters: * name - {String} A name for the layer * options - {Object} Optional object with non-default properties to set on * the layer. * * Returns: * {} A new vector layer */ initialize: function(name, options) { OpenLayers.Layer.prototype.initialize.apply(this, arguments); // allow user-set renderer, otherwise assign one if (!this.renderer || !this.renderer.supported()) { this.assignRenderer(); } // if no valid renderer found, display error if (!this.renderer || !this.renderer.supported()) { this.renderer = null; this.displayError(); } if (!this.styleMap) { this.styleMap = new OpenLayers.StyleMap(); } this.features = []; this.selectedFeatures = []; this.unrenderedFeatures = {}; // Allow for custom layer behavior if(this.strategies){ for(var i=0, len=this.strategies.length; i} An exact clone of this layer */ clone: function (obj) { if (obj == null) { obj = new OpenLayers.Layer.Vector(this.name, this.getOptions()); } //get all additions from superclasses obj = OpenLayers.Layer.prototype.clone.apply(this, [obj]); // copy/set any non-init, non-simple values here var features = this.features; var len = features.length; var clonedFeatures = new Array(len); for(var i=0; i} */ setMap: function(map) { OpenLayers.Layer.prototype.setMap.apply(this, arguments); if (!this.renderer) { this.map.removeLayer(this); } else { this.renderer.map = this.map; var newSize = this.map.getSize(); newSize.w = newSize.w * this.ratio; newSize.h = newSize.h * this.ratio; this.renderer.setSize(newSize); } }, /** * Method: afterAdd * Called at the end of the map.addLayer sequence. At this point, the map * will have a base layer. Any autoActivate strategies will be * activated here. */ afterAdd: function() { if(this.strategies) { var strategy, i, len; for(i=0, len=this.strategies.length; i} */ removeMap: function(map) { this.drawn = false; if(this.strategies) { var strategy, i, len; for(i=0, len=this.strategies.length; i} * zoomChanged - {Boolean} * dragging - {Boolean} */ moveTo: function(bounds, zoomChanged, dragging) { OpenLayers.Layer.prototype.moveTo.apply(this, arguments); var coordSysUnchanged = true; if (!dragging) { this.renderer.root.style.visibility = 'hidden'; var viewSize = this.map.getSize(), viewWidth = viewSize.w, viewHeight = viewSize.h, offsetLeft = (viewWidth / 2 * this.ratio) - viewWidth / 2, offsetTop = (viewHeight / 2 * this.ratio) - viewHeight / 2; offsetLeft += this.map.layerContainerOriginPx.x; offsetLeft = -Math.round(offsetLeft); offsetTop += this.map.layerContainerOriginPx.y; offsetTop = -Math.round(offsetTop); this.div.style.left = offsetLeft + 'px'; this.div.style.top = offsetTop + 'px'; var extent = this.map.getExtent().scale(this.ratio); coordSysUnchanged = this.renderer.setExtent(extent, zoomChanged); this.renderer.root.style.visibility = 'visible'; // Force a reflow on gecko based browsers to prevent jump/flicker. // This seems to happen on only certain configurations; it was originally // noticed in FF 2.0 and Linux. if (OpenLayers.IS_GECKO === true) { this.div.scrollLeft = this.div.scrollLeft; } if (!zoomChanged && coordSysUnchanged) { for (var i in this.unrenderedFeatures) { var feature = this.unrenderedFeatures[i]; this.drawFeature(feature); } } } if (!this.drawn || zoomChanged || !coordSysUnchanged) { this.drawn = true; var feature; for(var i=0, len=this.features.length; i)} * options - {Object} */ addFeatures: function(features, options) { if (!(OpenLayers.Util.isArray(features))) { features = [features]; } var notify = !options || !options.silent; if(notify) { var event = {features: features}; var ret = this.events.triggerEvent("beforefeaturesadded", event); if(ret === false) { return; } features = event.features; } // Track successfully added features for featuresadded event, since // beforefeatureadded can veto single features. var featuresAdded = []; for (var i=0, len=features.length; i)} List of features to be * removed. * options - {Object} Optional properties for changing behavior of the * removal. * * Valid options: * silent - {Boolean} Supress event triggering. Default is false. */ removeFeatures: function(features, options) { if(!features || features.length === 0) { return; } if (features === this.features) { return this.removeAllFeatures(options); } if (!(OpenLayers.Util.isArray(features))) { features = [features]; } if (features === this.selectedFeatures) { features = features.slice(); } var notify = !options || !options.silent; if (notify) { this.events.triggerEvent( "beforefeaturesremoved", {features: features} ); } for (var i = features.length - 1; i >= 0; i--) { // We remain locked so long as we're not at 0 // and the 'next' feature has a geometry. We do the geometry check // because if all the features after the current one are 'null', we // won't call eraseGeometry, so we break the 'renderer functions // will always be called with locked=false *last*' rule. The end result // is a possible gratiutious unlocking to save a loop through the rest // of the list checking the remaining features every time. So long as // null geoms are rare, this is probably okay. if (i != 0 && features[i-1].geometry) { this.renderer.locked = true; } else { this.renderer.locked = false; } var feature = features[i]; delete this.unrenderedFeatures[feature.id]; if (notify) { this.events.triggerEvent("beforefeatureremoved", { feature: feature }); } this.features = OpenLayers.Util.removeItem(this.features, feature); // feature has no layer at this point feature.layer = null; if (feature.geometry) { this.renderer.eraseFeatures(feature); } //in the case that this feature is one of the selected features, // remove it from that array as well. if (OpenLayers.Util.indexOf(this.selectedFeatures, feature) != -1){ OpenLayers.Util.removeItem(this.selectedFeatures, feature); } if (notify) { this.events.triggerEvent("featureremoved", { feature: feature }); } } if (notify) { this.events.triggerEvent("featuresremoved", {features: features}); } }, /** * APIMethod: removeAllFeatures * Remove all features from the layer. * * Parameters: * options - {Object} Optional properties for changing behavior of the * removal. * * Valid options: * silent - {Boolean} Supress event triggering. Default is false. */ removeAllFeatures: function(options) { var notify = !options || !options.silent; var features = this.features; if (notify) { this.events.triggerEvent( "beforefeaturesremoved", {features: features} ); } var feature; for (var i = features.length-1; i >= 0; i--) { feature = features[i]; if (notify) { this.events.triggerEvent("beforefeatureremoved", { feature: feature }); } feature.layer = null; if (notify) { this.events.triggerEvent("featureremoved", { feature: feature }); } } this.renderer.clear(); this.features = []; this.unrenderedFeatures = {}; this.selectedFeatures = []; if (notify) { this.events.triggerEvent("featuresremoved", {features: features}); } }, /** * APIMethod: destroyFeatures * Erase and destroy features on the layer. * * Parameters: * features - {Array()} An optional array of * features to destroy. If not supplied, all features on the layer * will be destroyed. * options - {Object} */ destroyFeatures: function(features, options) { var all = (features == undefined); // evaluates to true if // features is null if(all) { features = this.features; } if(features) { this.removeFeatures(features, options); for(var i=features.length-1; i>=0; i--) { features[i].destroy(); } } }, /** * APIMethod: drawFeature * Draw (or redraw) a feature on the layer. If the optional style argument * is included, this style will be used. If no style is included, the * feature's style will be used. If the feature doesn't have a style, * the layer's style will be used. * * This function is not designed to be used when adding features to * the layer (use addFeatures instead). It is meant to be used when * the style of a feature has changed, or in some other way needs to * visually updated *after* it has already been added to a layer. You * must add the feature to the layer for most layer-related events to * happen. * * Parameters: * feature - {} * style - {String | Object} Named render intent or full symbolizer object. */ drawFeature: function(feature, style) { // don't try to draw the feature with the renderer if the layer is not // drawn itself if (!this.drawn) { return; } if (typeof style != "object") { if(!style && feature.state === OpenLayers.State.DELETE) { style = "delete"; } var renderIntent = style || feature.renderIntent; style = feature.style || this.style; if (!style) { style = this.styleMap.createSymbolizer(feature, renderIntent); } } var drawn = this.renderer.drawFeature(feature, style); //TODO remove the check for null when we get rid of Renderer.SVG if (drawn === false || drawn === null) { this.unrenderedFeatures[feature.id] = feature; } else { delete this.unrenderedFeatures[feature.id]; } }, /** * Method: eraseFeatures * Erase features from the layer. * * Parameters: * features - {Array()} */ eraseFeatures: function(features) { this.renderer.eraseFeatures(features); }, /** * Method: getFeatureFromEvent * Given an event, return a feature if the event occurred over one. * Otherwise, return null. * * Parameters: * evt - {Event} * * Returns: * {} A feature if one was under the event. */ getFeatureFromEvent: function(evt) { if (!this.renderer) { throw new Error('getFeatureFromEvent called on layer with no ' + 'renderer. This usually means you destroyed a ' + 'layer, but not some handler which is associated ' + 'with it.'); } var feature = null; var featureId = this.renderer.getFeatureIdFromEvent(evt); if (featureId) { if (typeof featureId === "string") { feature = this.getFeatureById(featureId); } else { feature = featureId; } } return feature; }, /** * APIMethod: getFeatureBy * Given a property value, return the feature if it exists in the features array * * Parameters: * property - {String} * value - {String} * * Returns: * {} A feature corresponding to the given * property value or null if there is no such feature. */ getFeatureBy: function(property, value) { //TBD - would it be more efficient to use a hash for this.features? var feature = null; for(var i=0, len=this.features.length; i} A feature corresponding to the given * featureId or null if there is no such feature. */ getFeatureById: function(featureId) { return this.getFeatureBy('id', featureId); }, /** * APIMethod: getFeatureByFid * Given a feature fid, return the feature if it exists in the features array * * Parameters: * featureFid - {String} * * Returns: * {} A feature corresponding to the given * featureFid or null if there is no such feature. */ getFeatureByFid: function(featureFid) { return this.getFeatureBy('fid', featureFid); }, /** * APIMethod: getFeaturesByAttribute * Returns an array of features that have the given attribute key set to the * given value. Comparison of attribute values takes care of datatypes, e.g. * the string '1234' is not equal to the number 1234. * * Parameters: * attrName - {String} * attrValue - {Mixed} * * Returns: * Array({}) An array of features that have the * passed named attribute set to the given value. */ getFeaturesByAttribute: function(attrName, attrValue) { var i, feature, len = this.features.length, foundFeatures = []; for(i = 0; i < len; i++) { feature = this.features[i]; if(feature && feature.attributes) { if (feature.attributes[attrName] === attrValue) { foundFeatures.push(feature); } } } return foundFeatures; }, /** * Unselect the selected features * i.e. clears the featureSelection array * change the style back clearSelection: function() { var vectorLayer = this.map.vectorLayer; for (var i = 0; i < this.map.featureSelection.length; i++) { var featureSelection = this.map.featureSelection[i]; vectorLayer.drawFeature(featureSelection, vectorLayer.style); } this.map.featureSelection = []; }, */ /** * APIMethod: onFeatureInsert * method called after a feature is inserted. * Does nothing by default. Override this if you * need to do something on feature updates. * * Parameters: * feature - {} */ onFeatureInsert: function(feature) { }, /** * APIMethod: preFeatureInsert * method called before a feature is inserted. * Does nothing by default. Override this if you * need to do something when features are first added to the * layer, but before they are drawn, such as adjust the style. * * Parameters: * feature - {} */ preFeatureInsert: function(feature) { }, /** * APIMethod: getDataExtent * Calculates the max extent which includes all of the features. * * Returns: * {} or null if the layer has no features with * geometries. */ getDataExtent: function () { var maxExtent = null; var features = this.features; if(features && (features.length > 0)) { var geometry = null; for(var i=0, len=features.length; i */ 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()} The layers managed by this * container * * Returns: * {} 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: * {} */ getFeatureFromEvent: function(evt) { var layers = this.layers; var feature; for(var i=0; i} */ setMap: function(map) { OpenLayers.Layer.Vector.prototype.setMap.apply(this, arguments); this.collectRoots(); map.events.register("changelayer", this, this.handleChangeLayer); }, /** * Method: removeMap * * Parameters: * 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 */ OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, { /** * APIProperty: events * {} Events instance for listeners and triggering * control specific events. * * Register a listener for a particular event with the following syntax: * (code) * control.events.register(type, obj, listener); * (end) * * Supported event types (in addition to those from ): * 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 property to true. Default is null. */ multipleKey: null, /** * Property: toggleKey * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets * the 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 * {} 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()} 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 * instances. */ handlers: null, /** * Constructor: OpenLayers.Control.SelectFeature * Create a new control for selecting features. * * Parameters: * layers - {}, 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 - {}, 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 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 - {} */ 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 property and * 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 * property and 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 - {} */ 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 - {} */ 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 - {} */ 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 - {} */ 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 - {} */ 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 - {} */ 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 - {} */ 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 selection is true * on. * * Parameters: * position - { || } */ 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 -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 - {} */ 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 {} or a single * {} */ 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/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.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 . 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} 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 - {} 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/Filter/Logical.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Filter.js */ /** * Class: OpenLayers.Filter.Logical * This class represents ogc:And, ogc:Or and ogc:Not rules. * * Inherits from: * - */ OpenLayers.Filter.Logical = OpenLayers.Class(OpenLayers.Filter, { /** * APIProperty: filters * {Array()} Child filters for this filter. */ filters: null, /** * APIProperty: type * {String} type of logical operator. Available types are: * - OpenLayers.Filter.Logical.AND = "&&"; * - OpenLayers.Filter.Logical.OR = "||"; * - OpenLayers.Filter.Logical.NOT = "!"; */ type: null, /** * Constructor: OpenLayers.Filter.Logical * Creates a logical filter (And, Or, Not). * * Parameters: * options - {Object} An optional object with properties to set on the * filter. * * Returns: * {} */ initialize: function(options) { this.filters = []; OpenLayers.Filter.prototype.initialize.apply(this, [options]); }, /** * APIMethod: destroy * Remove reference to child filters. */ destroy: function() { this.filters = null; OpenLayers.Filter.prototype.destroy.apply(this); }, /** * APIMethod: evaluate * Evaluates this filter in a specific context. * * Parameters: * context - {Object} Context to use in evaluating the filter. A vector * feature may also be provided to evaluate feature attributes in * comparison filters or geometries in spatial filters. * * Returns: * {Boolean} The filter applies. */ evaluate: function(context) { var i, len; switch(this.type) { case OpenLayers.Filter.Logical.AND: for (i=0, len=this.filters.length; i} Clone of this filter. */ clone: function() { var filters = []; for(var i=0, len=this.filters.length; i constructor. * * Inherits from: * - */ OpenLayers.Handler.Drag = OpenLayers.Class(OpenLayers.Handler, { /** * Property: started * {Boolean} When a mousedown or touchstart event is received, we want to * record it, but not set 'dragging' until the mouse moves after starting. */ started: false, /** * Property: stopDown * {Boolean} Stop propagation of mousedown events from getting to listeners * on the same element. Default is true. */ stopDown: true, /** * Property: dragging * {Boolean} */ dragging: false, /** * Property: last * {} The last pixel location of the drag. */ last: null, /** * Property: start * {} The first pixel location of the drag. */ start: null, /** * Property: lastMoveEvt * {Object} The last mousemove event that occurred. Used to * position the map correctly when our "delay drag" * timeout expired. */ lastMoveEvt: null, /** * Property: oldOnselectstart * {Function} */ oldOnselectstart: null, /** * Property: interval * {Integer} In order to increase performance, an interval (in * milliseconds) can be set to reduce the number of drag events * called. If set, a new drag event will not be set until the * interval has passed. * Defaults to 0, meaning no interval. */ interval: 0, /** * Property: timeoutId * {String} The id of the timeout used for the mousedown interval. * This is "private", and should be left alone. */ timeoutId: null, /** * APIProperty: documentDrag * {Boolean} If set to true, the handler will also handle mouse moves when * the cursor has moved out of the map viewport. Default is false. */ documentDrag: false, /** * Property: documentEvents * {Boolean} Are we currently observing document events? */ documentEvents: null, /** * Constructor: OpenLayers.Handler.Drag * Returns OpenLayers.Handler.Drag * * Parameters: * control - {} The control that is making use of * this handler. If a handler is being used without a control, the * handlers setMap method must be overridden to deal properly with * the map. * callbacks - {Object} An object containing a single function to be * called when the drag operation is finished. The callback should * expect to recieve a single argument, the pixel location of the event. * Callbacks for 'move' and 'done' are supported. You can also speficy * callbacks for 'down', 'up', and 'out' to respond to those events. * options - {Object} */ initialize: function(control, callbacks, options) { OpenLayers.Handler.prototype.initialize.apply(this, arguments); if (this.documentDrag === true) { var me = this; this._docMove = function(evt) { me.mousemove({ xy: {x: evt.clientX, y: evt.clientY}, element: document }); }; this._docUp = function(evt) { me.mouseup({xy: {x: evt.clientX, y: evt.clientY}}); }; } }, /** * Method: dragstart * This private method is factorized from mousedown and touchstart methods * * Parameters: * evt - {Event} The event * * Returns: * {Boolean} Let the event propagate. */ dragstart: function (evt) { var propagate = true; this.dragging = false; if (this.checkModifiers(evt) && (OpenLayers.Event.isLeftClick(evt) || OpenLayers.Event.isSingleTouch(evt))) { this.started = true; this.start = evt.xy; this.last = evt.xy; OpenLayers.Element.addClass( this.map.viewPortDiv, "olDragDown" ); this.down(evt); this.callback("down", [evt.xy]); // prevent document dragging OpenLayers.Event.preventDefault(evt); if(!this.oldOnselectstart) { this.oldOnselectstart = document.onselectstart ? document.onselectstart : OpenLayers.Function.True; } document.onselectstart = OpenLayers.Function.False; propagate = !this.stopDown; } else { this.started = false; this.start = null; this.last = null; } return propagate; }, /** * Method: dragmove * This private method is factorized from mousemove and touchmove methods * * Parameters: * evt - {Event} The event * * Returns: * {Boolean} Let the event propagate. */ dragmove: function (evt) { this.lastMoveEvt = evt; if (this.started && !this.timeoutId && (evt.xy.x != this.last.x || evt.xy.y != this.last.y)) { if(this.documentDrag === true && this.documentEvents) { if(evt.element === document) { this.adjustXY(evt); // do setEvent manually because the documentEvents are not // registered with the map this.setEvent(evt); } else { this.removeDocumentEvents(); } } if (this.interval > 0) { this.timeoutId = setTimeout( OpenLayers.Function.bind(this.removeTimeout, this), this.interval); } this.dragging = true; this.move(evt); this.callback("move", [evt.xy]); if(!this.oldOnselectstart) { this.oldOnselectstart = document.onselectstart; document.onselectstart = OpenLayers.Function.False; } this.last = evt.xy; } return true; }, /** * Method: dragend * This private method is factorized from mouseup and touchend methods * * Parameters: * evt - {Event} The event * * Returns: * {Boolean} Let the event propagate. */ dragend: function (evt) { if (this.started) { if(this.documentDrag === true && this.documentEvents) { this.adjustXY(evt); this.removeDocumentEvents(); } var dragged = (this.start != this.last); this.started = false; this.dragging = false; OpenLayers.Element.removeClass( this.map.viewPortDiv, "olDragDown" ); this.up(evt); this.callback("up", [evt.xy]); if(dragged) { this.callback("done", [evt.xy]); } document.onselectstart = this.oldOnselectstart; } return true; }, /** * The four methods below (down, move, up, and out) are used by subclasses * to do their own processing related to these mouse events. */ /** * Method: down * This method is called during the handling of the mouse down event. * Subclasses can do their own processing here. * * Parameters: * evt - {Event} The mouse down event */ down: function(evt) { }, /** * Method: move * This method is called during the handling of the mouse move event. * Subclasses can do their own processing here. * * Parameters: * evt - {Event} The mouse move event * */ move: function(evt) { }, /** * Method: up * This method is called during the handling of the mouse up event. * Subclasses can do their own processing here. * * Parameters: * evt - {Event} The mouse up event */ up: function(evt) { }, /** * Method: out * This method is called during the handling of the mouse out event. * Subclasses can do their own processing here. * * Parameters: * evt - {Event} The mouse out event */ out: function(evt) { }, /** * The methods below are part of the magic of event handling. Because * they are named like browser events, they are registered as listeners * for the events they represent. */ /** * Method: mousedown * Handle mousedown events * * Parameters: * evt - {Event} * * Returns: * {Boolean} Let the event propagate. */ mousedown: function(evt) { return this.dragstart(evt); }, /** * Method: touchstart * Handle touchstart events * * Parameters: * evt - {Event} * * Returns: * {Boolean} Let the event propagate. */ touchstart: function(evt) { this.startTouch(); return this.dragstart(evt); }, /** * Method: mousemove * Handle mousemove events * * Parameters: * evt - {Event} * * Returns: * {Boolean} Let the event propagate. */ mousemove: function(evt) { return this.dragmove(evt); }, /** * Method: touchmove * Handle touchmove events * * Parameters: * evt - {Event} * * Returns: * {Boolean} Let the event propagate. */ touchmove: function(evt) { return this.dragmove(evt); }, /** * Method: removeTimeout * Private. Called by mousemove() to remove the drag timeout. */ removeTimeout: function() { this.timeoutId = null; // if timeout expires while we're still dragging (mouseup // hasn't occurred) then call mousemove to move to the // correct position if(this.dragging) { this.mousemove(this.lastMoveEvt); } }, /** * Method: mouseup * Handle mouseup events * * Parameters: * evt - {Event} * * Returns: * {Boolean} Let the event propagate. */ mouseup: function(evt) { return this.dragend(evt); }, /** * Method: touchend * Handle touchend events * * Parameters: * evt - {Event} * * Returns: * {Boolean} Let the event propagate. */ touchend: function(evt) { // override evt.xy with last position since touchend does not have // any touch position evt.xy = this.last; return this.dragend(evt); }, /** * Method: mouseout * Handle mouseout events * * Parameters: * evt - {Event} * * Returns: * {Boolean} Let the event propagate. */ mouseout: function (evt) { if (this.started && OpenLayers.Util.mouseLeft(evt, this.map.viewPortDiv)) { if(this.documentDrag === true) { this.addDocumentEvents(); } else { var dragged = (this.start != this.last); this.started = false; this.dragging = false; OpenLayers.Element.removeClass( this.map.viewPortDiv, "olDragDown" ); this.out(evt); this.callback("out", []); if(dragged) { this.callback("done", [evt.xy]); } if(document.onselectstart) { document.onselectstart = this.oldOnselectstart; } } } return true; }, /** * Method: click * The drag handler captures the click event. If something else registers * for clicks on the same element, its listener will not be called * after a drag. * * Parameters: * evt - {Event} * * Returns: * {Boolean} Let the event propagate. */ click: function (evt) { // let the click event propagate only if the mouse moved return (this.start == this.last); }, /** * Method: activate * Activate the handler. * * Returns: * {Boolean} The handler was successfully activated. */ activate: function() { var activated = false; if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) { this.dragging = false; activated = true; } return activated; }, /** * Method: deactivate * Deactivate the handler. * * Returns: * {Boolean} The handler was successfully deactivated. */ deactivate: function() { var deactivated = false; if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) { this.started = false; this.dragging = false; this.start = null; this.last = null; deactivated = true; OpenLayers.Element.removeClass( this.map.viewPortDiv, "olDragDown" ); } return deactivated; }, /** * Method: adjustXY * Converts event coordinates that are relative to the document body to * ones that are relative to the map viewport. The latter is the default in * OpenLayers. * * Parameters: * evt - {Object} */ adjustXY: function(evt) { var pos = OpenLayers.Util.pagePosition(this.map.viewPortDiv); evt.xy.x -= pos[0]; evt.xy.y -= pos[1]; }, /** * Method: addDocumentEvents * Start observing document events when documentDrag is true and the mouse * cursor leaves the map viewport while dragging. */ addDocumentEvents: function() { OpenLayers.Element.addClass(document.body, "olDragDown"); this.documentEvents = true; OpenLayers.Event.observe(document, "mousemove", this._docMove); OpenLayers.Event.observe(document, "mouseup", this._docUp); }, /** * Method: removeDocumentEvents * Stops observing document events when documentDrag is true and the mouse * cursor re-enters the map viewport while dragging. */ removeDocumentEvents: function() { OpenLayers.Element.removeClass(document.body, "olDragDown"); this.documentEvents = false; OpenLayers.Event.stopObserving(document, "mousemove", this._docMove); OpenLayers.Event.stopObserving(document, "mouseup", this._docUp); }, CLASS_NAME: "OpenLayers.Handler.Drag" }); /* ====================================================================== OpenLayers/Handler/Box.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of 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/Handler/Drag.js */ /** * Class: OpenLayers.Handler.Box * Handler for dragging a rectangle across the map. Box is displayed * on mouse down, moves on mouse move, and is finished on mouse up. * * Inherits from: * - */ OpenLayers.Handler.Box = OpenLayers.Class(OpenLayers.Handler, { /** * Property: dragHandler * {} */ dragHandler: null, /** * APIProperty: boxDivClassName * {String} The CSS class to use for drawing the box. Default is * olHandlerBoxZoomBox */ boxDivClassName: 'olHandlerBoxZoomBox', /** * Property: boxOffsets * {Object} Caches box offsets from css. This is used by the getBoxOffsets * method. */ boxOffsets: null, /** * Constructor: OpenLayers.Handler.Box * * Parameters: * control - {} * callbacks - {Object} An object with a properties whose values are * functions. Various callbacks described below. * options - {Object} * * Named callbacks: * start - Called when the box drag operation starts. * done - Called when the box drag operation is finished. * The callback should expect to receive a single argument, the box * bounds or a pixel. If the box dragging didn't span more than a 5 * pixel distance, a pixel will be returned instead of a bounds object. */ initialize: function(control, callbacks, options) { OpenLayers.Handler.prototype.initialize.apply(this, arguments); this.dragHandler = new OpenLayers.Handler.Drag( this, { down: this.startBox, move: this.moveBox, out: this.removeBox, up: this.endBox }, {keyMask: this.keyMask} ); }, /** * Method: destroy */ destroy: function() { OpenLayers.Handler.prototype.destroy.apply(this, arguments); if (this.dragHandler) { this.dragHandler.destroy(); this.dragHandler = null; } }, /** * Method: setMap */ setMap: function (map) { OpenLayers.Handler.prototype.setMap.apply(this, arguments); if (this.dragHandler) { this.dragHandler.setMap(map); } }, /** * Method: startBox * * Parameters: * xy - {} */ startBox: function (xy) { this.callback("start", []); this.zoomBox = OpenLayers.Util.createDiv('zoomBox', { x: -9999, y: -9999 }); this.zoomBox.className = this.boxDivClassName; this.zoomBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1; this.map.viewPortDiv.appendChild(this.zoomBox); OpenLayers.Element.addClass( this.map.viewPortDiv, "olDrawBox" ); }, /** * Method: moveBox */ moveBox: function (xy) { var startX = this.dragHandler.start.x; var startY = this.dragHandler.start.y; var deltaX = Math.abs(startX - xy.x); var deltaY = Math.abs(startY - xy.y); var offset = this.getBoxOffsets(); this.zoomBox.style.width = (deltaX + offset.width + 1) + "px"; this.zoomBox.style.height = (deltaY + offset.height + 1) + "px"; this.zoomBox.style.left = (xy.x < startX ? startX - deltaX - offset.left : startX - offset.left) + "px"; this.zoomBox.style.top = (xy.y < startY ? startY - deltaY - offset.top : startY - offset.top) + "px"; }, /** * Method: endBox */ endBox: function(end) { var result; if (Math.abs(this.dragHandler.start.x - end.x) > 5 || Math.abs(this.dragHandler.start.y - end.y) > 5) { var start = this.dragHandler.start; var top = Math.min(start.y, end.y); var bottom = Math.max(start.y, end.y); var left = Math.min(start.x, end.x); var right = Math.max(start.x, end.x); result = new OpenLayers.Bounds(left, bottom, right, top); } else { result = this.dragHandler.start.clone(); // i.e. OL.Pixel } this.removeBox(); this.callback("done", [result]); }, /** * Method: removeBox * Remove the zoombox from the screen and nullify our reference to it. */ removeBox: function() { this.map.viewPortDiv.removeChild(this.zoomBox); this.zoomBox = null; this.boxOffsets = null; OpenLayers.Element.removeClass( this.map.viewPortDiv, "olDrawBox" ); }, /** * Method: activate */ activate: function () { if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) { this.dragHandler.activate(); return true; } else { return false; } }, /** * Method: deactivate */ deactivate: function () { if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) { if (this.dragHandler.deactivate()) { if (this.zoomBox) { this.removeBox(); } } return true; } else { return false; } }, /** * Method: getBoxOffsets * Determines border offsets for a box, according to the box model. * * Returns: * {Object} an object with the following offsets: * - left * - right * - top * - bottom * - width * - height */ getBoxOffsets: function() { if (!this.boxOffsets) { // Determine the box model. If the testDiv's clientWidth is 3, then // the borders are outside and we are dealing with the w3c box // model. Otherwise, the browser uses the traditional box model and // the borders are inside the box bounds, leaving us with a // clientWidth of 1. var testDiv = document.createElement("div"); //testDiv.style.visibility = "hidden"; testDiv.style.position = "absolute"; testDiv.style.border = "1px solid black"; testDiv.style.width = "3px"; document.body.appendChild(testDiv); var w3cBoxModel = testDiv.clientWidth == 3; document.body.removeChild(testDiv); var left = parseInt(OpenLayers.Element.getStyle(this.zoomBox, "border-left-width")); var right = parseInt(OpenLayers.Element.getStyle( this.zoomBox, "border-right-width")); var top = parseInt(OpenLayers.Element.getStyle(this.zoomBox, "border-top-width")); var bottom = parseInt(OpenLayers.Element.getStyle( this.zoomBox, "border-bottom-width")); this.boxOffsets = { left: left, right: right, top: top, bottom: bottom, width: w3cBoxModel === false ? left + right : 0, height: w3cBoxModel === false ? top + bottom : 0 }; } return this.boxOffsets; }, CLASS_NAME: "OpenLayers.Handler.Box" }); /* ====================================================================== OpenLayers/Control/ZoomBox.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of 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/Box.js */ /** * Class: OpenLayers.Control.ZoomBox * The ZoomBox control enables zooming directly to a given extent, by drawing * a box on the map. The box is drawn by holding down shift, whilst dragging * the mouse. * * Inherits from: * - */ OpenLayers.Control.ZoomBox = OpenLayers.Class(OpenLayers.Control, { /** * Property: type * {OpenLayers.Control.TYPE} */ type: OpenLayers.Control.TYPE_TOOL, /** * Property: out * {Boolean} Should the control be used for zooming out? */ out: false, /** * APIProperty: keyMask * {Integer} Zoom only occurs if the keyMask matches the combination of * keys down. Use bitwise operators and one or more of the * constants to construct a keyMask. Leave null if * not used mask. Default is null. */ keyMask: null, /** * APIProperty: alwaysZoom * {Boolean} Always zoom in/out when box drawn, even if the zoom level does * not change. */ alwaysZoom: false, /** * APIProperty: zoomOnClick * {Boolean} Should we zoom when no box was dragged, i.e. the user only * clicked? Default is true. */ zoomOnClick: true, /** * Method: draw */ draw: function() { this.handler = new OpenLayers.Handler.Box( this, {done: this.zoomBox}, {keyMask: this.keyMask} ); }, /** * Method: zoomBox * * Parameters: * position - {} or {} */ zoomBox: function (position) { if (position instanceof OpenLayers.Bounds) { var bounds, targetCenterPx = position.getCenterPixel(); if (!this.out) { var minXY = this.map.getLonLatFromPixel({ x: position.left, y: position.bottom }); var maxXY = this.map.getLonLatFromPixel({ x: position.right, y: position.top }); bounds = new OpenLayers.Bounds(minXY.lon, minXY.lat, maxXY.lon, maxXY.lat); } else { var pixWidth = position.right - position.left; var pixHeight = position.bottom - position.top; var zoomFactor = Math.min((this.map.size.h / pixHeight), (this.map.size.w / pixWidth)); var extent = this.map.getExtent(); var center = this.map.getLonLatFromPixel(targetCenterPx); var xmin = center.lon - (extent.getWidth()/2)*zoomFactor; var xmax = center.lon + (extent.getWidth()/2)*zoomFactor; var ymin = center.lat - (extent.getHeight()/2)*zoomFactor; var ymax = center.lat + (extent.getHeight()/2)*zoomFactor; bounds = new OpenLayers.Bounds(xmin, ymin, xmax, ymax); } // always zoom in/out var lastZoom = this.map.getZoom(), size = this.map.getSize(), centerPx = {x: size.w / 2, y: size.h / 2}, zoom = this.map.getZoomForExtent(bounds), oldRes = this.map.getResolution(), newRes = this.map.getResolutionForZoom(zoom); if (oldRes == newRes) { this.map.setCenter(this.map.getLonLatFromPixel(targetCenterPx)); } else { var zoomOriginPx = { x: (oldRes * targetCenterPx.x - newRes * centerPx.x) / (oldRes - newRes), y: (oldRes * targetCenterPx.y - newRes * centerPx.y) / (oldRes - newRes) }; this.map.zoomTo(zoom, zoomOriginPx); } if (lastZoom == this.map.getZoom() && this.alwaysZoom == true){ this.map.zoomTo(lastZoom + (this.out ? -1 : 1)); } } else if (this.zoomOnClick) { // it's a pixel if (!this.out) { this.map.zoomTo(this.map.getZoom() + 1, position); } else { this.map.zoomTo(this.map.getZoom() - 1, position); } } }, CLASS_NAME: "OpenLayers.Control.ZoomBox" }); /* ====================================================================== 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.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 * {} 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 {} * 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 and * 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 - {} 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 ) * if the map has actually been moved. * * Parameters: * xy - {} 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/Handler/Click.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Handler.js */ /** * Class: OpenLayers.Handler.Click * A handler for mouse clicks. The intention of this handler is to give * controls more flexibility with handling clicks. Browsers trigger * click events twice for a double-click. In addition, the mousedown, * mousemove, mouseup sequence fires a click event. With this handler, * controls can decide whether to ignore clicks associated with a double * click. By setting a , controls can also ignore clicks * that include a drag. Create a new instance with the * constructor. * * Inherits from: * - */ OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, { /** * APIProperty: delay * {Number} Number of milliseconds between clicks before the event is * considered a double-click. */ delay: 300, /** * APIProperty: single * {Boolean} Handle single clicks. Default is true. If false, clicks * will not be reported. If true, single-clicks will be reported. */ single: true, /** * APIProperty: double * {Boolean} Handle double-clicks. Default is false. */ 'double': false, /** * APIProperty: pixelTolerance * {Number} Maximum number of pixels between mouseup and mousedown for an * event to be considered a click. Default is 0. If set to an * integer value, clicks with a drag greater than the value will be * ignored. This property can only be set when the handler is * constructed. */ pixelTolerance: 0, /** * APIProperty: dblclickTolerance * {Number} Maximum distance in pixels between clicks for a sequence of * events to be considered a double click. Default is 13. If the * distance between two clicks is greater than this value, a double- * click will not be fired. */ dblclickTolerance: 13, /** * APIProperty: stopSingle * {Boolean} Stop other listeners from being notified of clicks. Default * is false. If true, any listeners registered before this one for * click or rightclick events will not be notified. */ stopSingle: false, /** * APIProperty: stopDouble * {Boolean} Stop other listeners from being notified of double-clicks. * Default is false. If true, any click listeners registered before * this one will not be notified of *any* double-click events. * * The one caveat with stopDouble is that given a map with two click * handlers, one with stopDouble true and the other with stopSingle * true, the stopSingle handler should be activated last to get * uniform cross-browser performance. Since IE triggers one click * with a dblclick and FF triggers two, if a stopSingle handler is * activated first, all it gets in IE is a single click when the * second handler stops propagation on the dblclick. */ stopDouble: false, /** * Property: timerId * {Number} The id of the timeout waiting to clear the . */ timerId: null, /** * Property: down * {Object} Object that store relevant information about the last * mousedown or touchstart. Its 'xy' OpenLayers.Pixel property gives * the average location of the mouse/touch event. Its 'touches' * property records clientX/clientY of each touches. */ down: null, /** * Property: last * {Object} Object that store relevant information about the last * mousemove or touchmove. Its 'xy' OpenLayers.Pixel property gives * the average location of the mouse/touch event. Its 'touches' * property records clientX/clientY of each touches. */ last: null, /** * Property: first * {Object} When waiting for double clicks, this object will store * information about the first click in a two click sequence. */ first: null, /** * Property: rightclickTimerId * {Number} The id of the right mouse timeout waiting to clear the * . */ rightclickTimerId: null, /** * Constructor: OpenLayers.Handler.Click * Create a new click handler. * * Parameters: * control - {} The control that is making use of * this handler. If a handler is being used without a control, the * handler's setMap method must be overridden to deal properly with * the map. * callbacks - {Object} An object with keys corresponding to callbacks * that will be called by the handler. The callbacks should * expect to recieve a single argument, the click event. * Callbacks for 'click' and 'dblclick' are supported. * options - {Object} Optional object whose properties will be set on the * handler. */ /** * Method: touchstart * Handle touchstart. * * Returns: * {Boolean} Continue propagating this event. */ touchstart: function(evt) { this.startTouch(); this.down = this.getEventInfo(evt); this.last = this.getEventInfo(evt); return true; }, /** * Method: touchmove * Store position of last move, because touchend event can have * an empty "touches" property. * * Returns: * {Boolean} Continue propagating this event. */ touchmove: function(evt) { this.last = this.getEventInfo(evt); return true; }, /** * Method: touchend * Correctly set event xy property, and add lastTouches to have * touches property from last touchstart or touchmove * * Returns: * {Boolean} Continue propagating this event. */ touchend: function(evt) { // touchstart may not have been allowed to propagate if (this.down) { evt.xy = this.last.xy; evt.lastTouches = this.last.touches; this.handleSingle(evt); this.down = null; } return true; }, /** * Method: mousedown * Handle mousedown. * * Returns: * {Boolean} Continue propagating this event. */ mousedown: function(evt) { this.down = this.getEventInfo(evt); this.last = this.getEventInfo(evt); return true; }, /** * Method: mouseup * Handle mouseup. Installed to support collection of right mouse events. * * Returns: * {Boolean} Continue propagating this event. */ mouseup: function (evt) { var propagate = true; // Collect right mouse clicks from the mouseup // IE - ignores the second right click in mousedown so using // mouseup instead if (this.checkModifiers(evt) && this.control.handleRightClicks && OpenLayers.Event.isRightClick(evt)) { propagate = this.rightclick(evt); } return propagate; }, /** * Method: rightclick * Handle rightclick. For a dblrightclick, we get two clicks so we need * to always register for dblrightclick to properly handle single * clicks. * * Returns: * {Boolean} Continue propagating this event. */ rightclick: function(evt) { if(this.passesTolerance(evt)) { if(this.rightclickTimerId != null) { //Second click received before timeout this must be // a double click this.clearTimer(); this.callback('dblrightclick', [evt]); return !this.stopDouble; } else { //Set the rightclickTimerId, send evt only if double is // true else trigger single var clickEvent = this['double'] ? OpenLayers.Util.extend({}, evt) : this.callback('rightclick', [evt]); var delayedRightCall = OpenLayers.Function.bind( this.delayedRightCall, this, clickEvent ); this.rightclickTimerId = window.setTimeout( delayedRightCall, this.delay ); } } return !this.stopSingle; }, /** * Method: delayedRightCall * Sets to null. And optionally triggers the * rightclick callback if evt is set. */ delayedRightCall: function(evt) { this.rightclickTimerId = null; if (evt) { this.callback('rightclick', [evt]); } }, /** * Method: click * Handle click events from the browser. This is registered as a listener * for click events and should not be called from other events in this * handler. * * Returns: * {Boolean} Continue propagating this event. */ click: function(evt) { if (!this.last) { this.last = this.getEventInfo(evt); } this.handleSingle(evt); return !this.stopSingle; }, /** * Method: dblclick * Handle dblclick. For a dblclick, we get two clicks in some browsers * (FF) and one in others (IE). So we need to always register for * dblclick to properly handle single clicks. This method is registered * as a listener for the dblclick browser event. It should *not* be * called by other methods in this handler. * * Returns: * {Boolean} Continue propagating this event. */ dblclick: function(evt) { this.handleDouble(evt); return !this.stopDouble; }, /** * Method: handleDouble * Handle double-click sequence. */ handleDouble: function(evt) { if (this.passesDblclickTolerance(evt)) { if (this["double"]) { this.callback("dblclick", [evt]); } // to prevent a dblclick from firing the click callback in IE this.clearTimer(); } }, /** * Method: handleSingle * Handle single click sequence. */ handleSingle: function(evt) { if (this.passesTolerance(evt)) { if (this.timerId != null) { // already received a click if (this.last.touches && this.last.touches.length === 1) { // touch device, no dblclick event - this may be a double if (this["double"]) { // on Android don't let the browser zoom on the page OpenLayers.Event.preventDefault(evt); } this.handleDouble(evt); } // if we're not in a touch environment we clear the click timer // if we've got a second touch, we'll get two touchend events if (!this.last.touches || this.last.touches.length !== 2) { this.clearTimer(); } } else { // remember the first click info so we can compare to the second this.first = this.getEventInfo(evt); // set the timer, send evt only if single is true //use a clone of the event object because it will no longer //be a valid event object in IE in the timer callback var clickEvent = this.single ? OpenLayers.Util.extend({}, evt) : null; this.queuePotentialClick(clickEvent); } } }, /** * Method: queuePotentialClick * This method is separated out largely to make testing easier (so we * don't have to override window.setTimeout) */ queuePotentialClick: function(evt) { this.timerId = window.setTimeout( OpenLayers.Function.bind(this.delayedCall, this, evt), this.delay ); }, /** * Method: passesTolerance * Determine whether the event is within the optional pixel tolerance. Note * that the pixel tolerance check only works if mousedown events get to * the listeners registered here. If they are stopped by other elements, * the will have no effect here (this method will always * return true). * * Returns: * {Boolean} The click is within the pixel tolerance (if specified). */ passesTolerance: function(evt) { var passes = true; if (this.pixelTolerance != null && this.down && this.down.xy) { passes = this.pixelTolerance >= this.down.xy.distanceTo(evt.xy); // for touch environments, we also enforce that all touches // start and end within the given tolerance to be considered a click if (passes && this.touch && this.down.touches.length === this.last.touches.length) { // the touchend event doesn't come with touches, so we check // down and last for (var i=0, ii=this.down.touches.length; i this.pixelTolerance) { passes = false; break; } } } } return passes; }, /** * Method: getTouchDistance * * Returns: * {Boolean} The pixel displacement between two touches. */ getTouchDistance: function(from, to) { return Math.sqrt( Math.pow(from.clientX - to.clientX, 2) + Math.pow(from.clientY - to.clientY, 2) ); }, /** * Method: passesDblclickTolerance * Determine whether the event is within the optional double-cick pixel * tolerance. * * Returns: * {Boolean} The click is within the double-click pixel tolerance. */ passesDblclickTolerance: function(evt) { var passes = true; if (this.down && this.first) { passes = this.down.xy.distanceTo(this.first.xy) <= this.dblclickTolerance; } return passes; }, /** * Method: clearTimer * Clear the timer and set to null. */ clearTimer: function() { if (this.timerId != null) { window.clearTimeout(this.timerId); this.timerId = null; } if (this.rightclickTimerId != null) { window.clearTimeout(this.rightclickTimerId); this.rightclickTimerId = null; } }, /** * Method: delayedCall * Sets to null. And optionally triggers the click callback if * evt is set. */ delayedCall: function(evt) { this.timerId = null; if (evt) { this.callback("click", [evt]); } }, /** * Method: getEventInfo * This method allows us to store event information without storing the * actual event. In touch devices (at least), the same event is * modified between touchstart, touchmove, and touchend. * * Returns: * {Object} An object with event related info. */ getEventInfo: function(evt) { var touches; if (evt.touches) { var len = evt.touches.length; touches = new Array(len); var touch; for (var i=0; i control. * * Note that this control is added to the map by default (if no controls * array is sent in the options object to the * constructor). * * Inherits: * - */ OpenLayers.Control.Navigation = OpenLayers.Class(OpenLayers.Control, { /** * Property: dragPan * {} */ dragPan: null, /** * APIProperty: dragPanOptions * {Object} Options passed to the DragPan control. */ dragPanOptions: null, /** * Property: pinchZoom * {} */ pinchZoom: null, /** * APIProperty: pinchZoomOptions * {Object} Options passed to the PinchZoom control. */ pinchZoomOptions: null, /** * APIProperty: documentDrag * {Boolean} Allow panning of the map by dragging outside map viewport. * Default is false. */ documentDrag: false, /** * Property: zoomBox * {} */ zoomBox: null, /** * APIProperty: zoomBoxEnabled * {Boolean} Whether the user can draw a box to zoom */ zoomBoxEnabled: true, /** * APIProperty: zoomWheelEnabled * {Boolean} Whether the mousewheel should zoom the map */ zoomWheelEnabled: true, /** * Property: mouseWheelOptions * {Object} Options passed to the MouseWheel control (only useful if * is set to true). Default is no options for maps * with fractionalZoom set to true, otherwise * {cumulative: false, interval: 50, maxDelta: 6} */ mouseWheelOptions: null, /** * APIProperty: handleRightClicks * {Boolean} Whether or not to handle right clicks. Default is false. */ handleRightClicks: false, /** * APIProperty: zoomBoxKeyMask * {Integer} key code of the key, which has to be * pressed, while drawing the zoom box with the mouse on the screen. * You should probably set handleRightClicks to true if you use this * with MOD_CTRL, to disable the context menu for machines which use * CTRL-Click as a right click. * Default: */ zoomBoxKeyMask: OpenLayers.Handler.MOD_SHIFT, /** * APIProperty: autoActivate * {Boolean} Activate the control when it is added to a map. Default is * true. */ autoActivate: true, /** * Constructor: OpenLayers.Control.Navigation * 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.zoomBox) { this.zoomBox.destroy(); } this.zoomBox = null; if (this.pinchZoom) { this.pinchZoom.destroy(); } this.pinchZoom = null; OpenLayers.Control.prototype.destroy.apply(this,arguments); }, /** * Method: activate */ activate: function() { this.dragPan.activate(); if (this.zoomWheelEnabled) { this.handlers.wheel.activate(); } this.handlers.click.activate(); if (this.zoomBoxEnabled) { this.zoomBox.activate(); } if (this.pinchZoom) { this.pinchZoom.activate(); } return OpenLayers.Control.prototype.activate.apply(this,arguments); }, /** * Method: deactivate */ deactivate: function() { if (this.pinchZoom) { this.pinchZoom.deactivate(); } this.zoomBox.deactivate(); this.dragPan.deactivate(); this.handlers.click.deactivate(); this.handlers.wheel.deactivate(); return OpenLayers.Control.prototype.deactivate.apply(this,arguments); }, /** * Method: draw */ draw: function() { // disable right mouse context menu for support of right click events if (this.handleRightClicks) { this.map.viewPortDiv.oncontextmenu = OpenLayers.Function.False; } var clickCallbacks = { 'click': this.defaultClick, 'dblclick': this.defaultDblClick, 'dblrightclick': this.defaultDblRightClick }; var clickOptions = { 'double': true, 'stopDouble': true }; 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.zoomBox = new OpenLayers.Control.ZoomBox( {map: this.map, keyMask: this.zoomBoxKeyMask}); this.dragPan.draw(); this.zoomBox.draw(); var wheelOptions = this.map.fractionalZoom ? {} : { cumulative: false, interval: 50, maxDelta: 6 }; this.handlers.wheel = new OpenLayers.Handler.MouseWheel( this, {up : this.wheelUp, down: this.wheelDown}, OpenLayers.Util.extend(wheelOptions, this.mouseWheelOptions) ); if (OpenLayers.Control.PinchZoom) { 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); }, /** * Method: defaultDblRightClick * * Parameters: * evt - {Event} */ defaultDblRightClick: function (evt) { this.map.zoomTo(this.map.zoom - 1, evt.xy); }, /** * Method: wheelChange * * Parameters: * evt - {Event} * deltaZ - {Integer} */ wheelChange: function(evt, deltaZ) { if (!this.map.fractionalZoom) { deltaZ = Math.round(deltaZ); } var currentZoom = this.map.getZoom(), newZoom = currentZoom + deltaZ; newZoom = Math.max(newZoom, 0); newZoom = Math.min(newZoom, this.map.getNumZoomLevels()); if (newZoom === currentZoom) { return; } this.map.zoomTo(newZoom, evt.xy); }, /** * Method: wheelUp * User spun scroll wheel up * * Parameters: * evt - {Event} * delta - {Integer} */ wheelUp: function(evt, delta) { this.wheelChange(evt, delta || 1); }, /** * Method: wheelDown * User spun scroll wheel down * * Parameters: * evt - {Event} * delta - {Integer} */ wheelDown: function(evt, delta) { this.wheelChange(evt, delta || -1); }, /** * Method: disableZoomBox */ disableZoomBox : function() { this.zoomBoxEnabled = false; this.zoomBox.deactivate(); }, /** * Method: enableZoomBox */ enableZoomBox : function() { this.zoomBoxEnabled = true; if (this.active) { this.zoomBox.activate(); } }, /** * Method: disableZoomWheel */ disableZoomWheel : function() { this.zoomWheelEnabled = false; this.handlers.wheel.deactivate(); }, /** * Method: enableZoomWheel */ enableZoomWheel : function() { this.zoomWheelEnabled = true; if (this.active) { this.handlers.wheel.activate(); } }, CLASS_NAME: "OpenLayers.Control.Navigation" }); /* ====================================================================== 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 * constructor. * * Inherits from: * - */ 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 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 ). * 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: * {} 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 - {} 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.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 - {} * 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 - {} 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 - {} * 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 - {} * * 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 - {} * 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 - {} * * 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 - {} * * 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 - {} * * 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} * * 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 - {} */ 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()} 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 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 - {} original geometry of the * invalid point * goodComponent - {} 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 - {} * * 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 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 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/Popup.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of 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.Popup * A popup is a small div that can opened and closed on the map. * Typically opened in response to clicking on a marker. * See . Popup's don't require their own * layer and are added the the map using the * method. * * Example: * (code) * popup = new OpenLayers.Popup("chicken", * new OpenLayers.LonLat(5,40), * new OpenLayers.Size(200,200), * "example popup", * true); * * map.addPopup(popup); * (end) */ OpenLayers.Popup = OpenLayers.Class({ /** * Property: events * {} custom event manager */ events: null, /** Property: id * {String} the unique identifier assigned to this popup. */ id: "", /** * Property: lonlat * {} the position of this popup on the map */ lonlat: null, /** * Property: div * {DOMElement} the div that contains this popup. */ div: null, /** * Property: contentSize * {} the width and height of the content. */ contentSize: null, /** * Property: size * {} the width and height of the popup. */ size: null, /** * Property: contentHTML * {String} An HTML string for this popup to display. */ contentHTML: null, /** * Property: backgroundColor * {String} the background color used by the popup. */ backgroundColor: "", /** * Property: opacity * {float} the opacity of this popup (between 0.0 and 1.0) */ opacity: "", /** * Property: border * {String} the border size of the popup. (eg 2px) */ border: "", /** * Property: contentDiv * {DOMElement} a reference to the element that holds the content of * the div. */ contentDiv: null, /** * Property: groupDiv * {DOMElement} First and only child of 'div'. The group Div contains the * 'contentDiv' and the 'closeDiv'. */ groupDiv: null, /** * Property: closeDiv * {DOMElement} the optional closer image */ closeDiv: null, /** * APIProperty: autoSize * {Boolean} Resize the popup to auto-fit the contents. * Default is false. */ autoSize: false, /** * APIProperty: minSize * {} Minimum size allowed for the popup's contents. */ minSize: null, /** * APIProperty: maxSize * {} Maximum size allowed for the popup's contents. */ maxSize: null, /** * Property: displayClass * {String} The CSS class of the popup. */ displayClass: "olPopup", /** * Property: contentDisplayClass * {String} The CSS class of the popup content div. */ contentDisplayClass: "olPopupContent", /** * Property: padding * {int or } An extra opportunity to specify internal * padding of the content div inside the popup. This was originally * confused with the css padding as specified in style.css's * 'olPopupContent' class. We would like to get rid of this altogether, * except that it does come in handy for the framed and anchoredbubble * popups, who need to maintain yet another barrier between their * content and the outer border of the popup itself. * * Note that in order to not break API, we must continue to support * this property being set as an integer. Really, though, we'd like to * have this specified as a Bounds object so that user can specify * distinct left, top, right, bottom paddings. With the 3.0 release * we can make this only a bounds. */ padding: 0, /** * Property: disableFirefoxOverflowHack * {Boolean} The hack for overflow in Firefox causes all elements * to be re-drawn, which causes Flash elements to be * re-initialized, which is troublesome. * With this property the hack can be disabled. */ disableFirefoxOverflowHack: false, /** * Method: fixPadding * To be removed in 3.0, this function merely helps us to deal with the * case where the user may have set an integer value for padding, * instead of an object. */ fixPadding: function() { if (typeof this.padding == "number") { this.padding = new OpenLayers.Bounds( this.padding, this.padding, this.padding, this.padding ); } }, /** * APIProperty: panMapIfOutOfView * {Boolean} When drawn, pan map such that the entire popup is visible in * the current viewport (if necessary). * Default is false. */ panMapIfOutOfView: false, /** * APIProperty: keepInMap * {Boolean} If panMapIfOutOfView is false, and this property is true, * contrain the popup such that it always fits in the available map * space. By default, this is not set on the base class. If you are * creating popups that are near map edges and not allowing pannning, * and especially if you have a popup which has a * fixedRelativePosition, setting this to false may be a smart thing to * do. Subclasses may want to override this setting. * * Default is false. */ keepInMap: false, /** * APIProperty: closeOnMove * {Boolean} When map pans, close the popup. * Default is false. */ closeOnMove: false, /** * Property: map * {} this gets set in Map.js when the popup is added to the map */ map: null, /** * Constructor: OpenLayers.Popup * Create a popup. * * Parameters: * id - {String} a unqiue identifier for this popup. If null is passed * an identifier will be automatically generated. * lonlat - {} The position on the map the popup will * be shown. * contentSize - {} The size of the content. * contentHTML - {String} An HTML string to display inside the * popup. * closeBox - {Boolean} Whether to display a close box inside * the popup. * closeBoxCallback - {Function} Function to be called on closeBox click. */ initialize:function(id, lonlat, contentSize, contentHTML, closeBox, closeBoxCallback) { if (id == null) { id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_"); } this.id = id; this.lonlat = lonlat; this.contentSize = (contentSize != null) ? contentSize : new OpenLayers.Size( OpenLayers.Popup.WIDTH, OpenLayers.Popup.HEIGHT); if (contentHTML != null) { this.contentHTML = contentHTML; } this.backgroundColor = OpenLayers.Popup.COLOR; this.opacity = OpenLayers.Popup.OPACITY; this.border = OpenLayers.Popup.BORDER; this.div = OpenLayers.Util.createDiv(this.id, null, null, null, null, null, "hidden"); this.div.className = this.displayClass; var groupDivId = this.id + "_GroupDiv"; this.groupDiv = OpenLayers.Util.createDiv(groupDivId, null, null, null, "relative", null, "hidden"); var id = this.div.id + "_contentDiv"; this.contentDiv = OpenLayers.Util.createDiv(id, null, this.contentSize.clone(), null, "relative"); this.contentDiv.className = this.contentDisplayClass; this.groupDiv.appendChild(this.contentDiv); this.div.appendChild(this.groupDiv); if (closeBox) { this.addCloseBox(closeBoxCallback); } this.registerEvents(); }, /** * Method: destroy * nullify references to prevent circular references and memory leaks */ destroy: function() { this.id = null; this.lonlat = null; this.size = null; this.contentHTML = null; this.backgroundColor = null; this.opacity = null; this.border = null; if (this.closeOnMove && this.map) { this.map.events.unregister("movestart", this, this.hide); } this.events.destroy(); this.events = null; if (this.closeDiv) { OpenLayers.Event.stopObservingElement(this.closeDiv); this.groupDiv.removeChild(this.closeDiv); } this.closeDiv = null; this.div.removeChild(this.groupDiv); this.groupDiv = null; if (this.map != null) { this.map.removePopup(this); } this.map = null; this.div = null; this.autoSize = null; this.minSize = null; this.maxSize = null; this.padding = null; this.panMapIfOutOfView = null; }, /** * Method: draw * Constructs the elements that make up the popup. * * Parameters: * px - {} the position the popup in pixels. * * Returns: * {DOMElement} Reference to a div that contains the drawn popup */ draw: function(px) { if (px == null) { if ((this.lonlat != null) && (this.map != null)) { px = this.map.getLayerPxFromLonLat(this.lonlat); } } // this assumes that this.map already exists, which is okay because // this.draw is only called once the popup has been added to the map. if (this.closeOnMove) { this.map.events.register("movestart", this, this.hide); } //listen to movestart, moveend to disable overflow (FF bug) if (!this.disableFirefoxOverflowHack && OpenLayers.BROWSER_NAME == 'firefox') { this.map.events.register("movestart", this, function() { var style = document.defaultView.getComputedStyle( this.contentDiv, null ); var currentOverflow = style.getPropertyValue("overflow"); if (currentOverflow != "hidden") { this.contentDiv._oldOverflow = currentOverflow; this.contentDiv.style.overflow = "hidden"; } }); this.map.events.register("moveend", this, function() { var oldOverflow = this.contentDiv._oldOverflow; if (oldOverflow) { this.contentDiv.style.overflow = oldOverflow; this.contentDiv._oldOverflow = null; } }); } this.moveTo(px); if (!this.autoSize && !this.size) { this.setSize(this.contentSize); } this.setBackgroundColor(); this.setOpacity(); this.setBorder(); this.setContentHTML(); if (this.panMapIfOutOfView) { this.panIntoView(); } return this.div; }, /** * Method: updatePosition * if the popup has a lonlat and its map members set, * then have it move itself to its proper position */ updatePosition: function() { if ((this.lonlat) && (this.map)) { var px = this.map.getLayerPxFromLonLat(this.lonlat); if (px) { this.moveTo(px); } } }, /** * Method: moveTo * * Parameters: * px - {} the top and left position of the popup div. */ moveTo: function(px) { if ((px != null) && (this.div != null)) { this.div.style.left = px.x + "px"; this.div.style.top = px.y + "px"; } }, /** * Method: visible * * Returns: * {Boolean} Boolean indicating whether or not the popup is visible */ visible: function() { return OpenLayers.Element.visible(this.div); }, /** * Method: toggle * Toggles visibility of the popup. */ toggle: function() { if (this.visible()) { this.hide(); } else { this.show(); } }, /** * Method: show * Makes the popup visible. */ show: function() { this.div.style.display = ''; if (this.panMapIfOutOfView) { this.panIntoView(); } }, /** * Method: hide * Makes the popup invisible. */ hide: function() { this.div.style.display = 'none'; }, /** * Method: setSize * Used to adjust the size of the popup. * * Parameters: * contentSize - {} the new size for the popup's * contents div (in pixels). */ setSize:function(contentSize) { this.size = contentSize.clone(); // if our contentDiv has a css 'padding' set on it by a stylesheet, we // must add that to the desired "size". var contentDivPadding = this.getContentDivPadding(); var wPadding = contentDivPadding.left + contentDivPadding.right; var hPadding = contentDivPadding.top + contentDivPadding.bottom; // take into account the popup's 'padding' property this.fixPadding(); wPadding += this.padding.left + this.padding.right; hPadding += this.padding.top + this.padding.bottom; // make extra space for the close div if (this.closeDiv) { var closeDivWidth = parseInt(this.closeDiv.style.width); wPadding += closeDivWidth + contentDivPadding.right; } //increase size of the main popup div to take into account the // users's desired padding and close div. this.size.w += wPadding; this.size.h += hPadding; //now if our browser is IE, we need to actually make the contents // div itself bigger to take its own padding into effect. this makes // me want to shoot someone, but so it goes. if (OpenLayers.BROWSER_NAME == "msie") { this.contentSize.w += contentDivPadding.left + contentDivPadding.right; this.contentSize.h += contentDivPadding.bottom + contentDivPadding.top; } if (this.div != null) { this.div.style.width = this.size.w + "px"; this.div.style.height = this.size.h + "px"; } if (this.contentDiv != null){ this.contentDiv.style.width = contentSize.w + "px"; this.contentDiv.style.height = contentSize.h + "px"; } }, /** * APIMethod: updateSize * Auto size the popup so that it precisely fits its contents (as * determined by this.contentDiv.innerHTML). Popup size will, of * course, be limited by the available space on the current map */ updateSize: function() { // determine actual render dimensions of the contents by putting its // contents into a fake contentDiv (for the CSS) and then measuring it var preparedHTML = "
" + this.contentDiv.innerHTML + "
"; var containerElement = (this.map) ? this.map.div : document.body; var realSize = OpenLayers.Util.getRenderedDimensions( preparedHTML, null, { displayClass: this.displayClass, containerElement: containerElement } ); // is the "real" size of the div is safe to display in our map? var safeSize = this.getSafeContentSize(realSize); var newSize = null; if (safeSize.equals(realSize)) { //real size of content is small enough to fit on the map, // so we use real size. newSize = realSize; } else { // make a new 'size' object with the clipped dimensions // set or null if not clipped. var fixedSize = { w: (safeSize.w < realSize.w) ? safeSize.w : null, h: (safeSize.h < realSize.h) ? safeSize.h : null }; if (fixedSize.w && fixedSize.h) { //content is too big in both directions, so we will use // max popup size (safeSize), knowing well that it will // overflow both ways. newSize = safeSize; } else { //content is clipped in only one direction, so we need to // run getRenderedDimensions() again with a fixed dimension var clippedSize = OpenLayers.Util.getRenderedDimensions( preparedHTML, fixedSize, { displayClass: this.contentDisplayClass, containerElement: containerElement } ); //if the clipped size is still the same as the safeSize, // that means that our content must be fixed in the // offending direction. If overflow is 'auto', this means // we are going to have a scrollbar for sure, so we must // adjust for that. // var currentOverflow = OpenLayers.Element.getStyle( this.contentDiv, "overflow" ); if ( (currentOverflow != "hidden") && (clippedSize.equals(safeSize)) ) { var scrollBar = OpenLayers.Util.getScrollbarWidth(); if (fixedSize.w) { clippedSize.h += scrollBar; } else { clippedSize.w += scrollBar; } } newSize = this.getSafeContentSize(clippedSize); } } this.setSize(newSize); }, /** * Method: setBackgroundColor * Sets the background color of the popup. * * Parameters: * color - {String} the background color. eg "#FFBBBB" */ setBackgroundColor:function(color) { if (color != undefined) { this.backgroundColor = color; } if (this.div != null) { this.div.style.backgroundColor = this.backgroundColor; } }, /** * Method: setOpacity * Sets the opacity of the popup. * * Parameters: * opacity - {float} A value between 0.0 (transparent) and 1.0 (solid). */ setOpacity:function(opacity) { if (opacity != undefined) { this.opacity = opacity; } if (this.div != null) { // for Mozilla and Safari this.div.style.opacity = this.opacity; // for IE this.div.style.filter = 'alpha(opacity=' + this.opacity*100 + ')'; } }, /** * Method: setBorder * Sets the border style of the popup. * * Parameters: * border - {String} The border style value. eg 2px */ setBorder:function(border) { if (border != undefined) { this.border = border; } if (this.div != null) { this.div.style.border = this.border; } }, /** * Method: setContentHTML * Allows the user to set the HTML content of the popup. * * Parameters: * contentHTML - {String} HTML for the div. */ setContentHTML:function(contentHTML) { if (contentHTML != null) { this.contentHTML = contentHTML; } if ((this.contentDiv != null) && (this.contentHTML != null) && (this.contentHTML != this.contentDiv.innerHTML)) { this.contentDiv.innerHTML = this.contentHTML; if (this.autoSize) { //if popup has images, listen for when they finish // loading and resize accordingly this.registerImageListeners(); //auto size the popup to its current contents this.updateSize(); } } }, /** * Method: registerImageListeners * Called when an image contained by the popup loaded. this function * updates the popup size, then unregisters the image load listener. */ registerImageListeners: function() { // As the images load, this function will call updateSize() to // resize the popup to fit the content div (which presumably is now // bigger than when the image was not loaded). // // If the 'panMapIfOutOfView' property is set, we will pan the newly // resized popup back into view. // // Note that this function, when called, will have 'popup' and // 'img' properties in the context. // var onImgLoad = function() { if (this.popup.id === null) { // this.popup has been destroyed! return; } this.popup.updateSize(); if ( this.popup.visible() && this.popup.panMapIfOutOfView ) { this.popup.panIntoView(); } OpenLayers.Event.stopObserving( this.img, "load", this.img._onImgLoad ); }; //cycle through the images and if their size is 0x0, that means that // they haven't been loaded yet, so we attach the listener, which // will fire when the images finish loading and will resize the // popup accordingly to its new size. var images = this.contentDiv.getElementsByTagName("img"); for (var i = 0, len = images.length; i < len; i++) { var img = images[i]; if (img.width == 0 || img.height == 0) { var context = { 'popup': this, 'img': img }; //expando this function to the image itself before registering // it. This way we can easily and properly unregister it. img._onImgLoad = OpenLayers.Function.bind(onImgLoad, context); OpenLayers.Event.observe(img, 'load', img._onImgLoad); } } }, /** * APIMethod: getSafeContentSize * * Parameters: * size - {} Desired size to make the popup. * * Returns: * {} A size to make the popup which is neither smaller * than the specified minimum size, nor bigger than the maximum * size (which is calculated relative to the size of the viewport). */ getSafeContentSize: function(size) { var safeContentSize = size.clone(); // if our contentDiv has a css 'padding' set on it by a stylesheet, we // must add that to the desired "size". var contentDivPadding = this.getContentDivPadding(); var wPadding = contentDivPadding.left + contentDivPadding.right; var hPadding = contentDivPadding.top + contentDivPadding.bottom; // take into account the popup's 'padding' property this.fixPadding(); wPadding += this.padding.left + this.padding.right; hPadding += this.padding.top + this.padding.bottom; if (this.closeDiv) { var closeDivWidth = parseInt(this.closeDiv.style.width); wPadding += closeDivWidth + contentDivPadding.right; } // prevent the popup from being smaller than a specified minimal size if (this.minSize) { safeContentSize.w = Math.max(safeContentSize.w, (this.minSize.w - wPadding)); safeContentSize.h = Math.max(safeContentSize.h, (this.minSize.h - hPadding)); } // prevent the popup from being bigger than a specified maximum size if (this.maxSize) { safeContentSize.w = Math.min(safeContentSize.w, (this.maxSize.w - wPadding)); safeContentSize.h = Math.min(safeContentSize.h, (this.maxSize.h - hPadding)); } //make sure the desired size to set doesn't result in a popup that // is bigger than the map's viewport. // if (this.map && this.map.size) { var extraX = 0, extraY = 0; if (this.keepInMap && !this.panMapIfOutOfView) { var px = this.map.getPixelFromLonLat(this.lonlat); switch (this.relativePosition) { case "tr": extraX = px.x; extraY = this.map.size.h - px.y; break; case "tl": extraX = this.map.size.w - px.x; extraY = this.map.size.h - px.y; break; case "bl": extraX = this.map.size.w - px.x; extraY = px.y; break; case "br": extraX = px.x; extraY = px.y; break; default: extraX = px.x; extraY = this.map.size.h - px.y; break; } } var maxY = this.map.size.h - this.map.paddingForPopups.top - this.map.paddingForPopups.bottom - hPadding - extraY; var maxX = this.map.size.w - this.map.paddingForPopups.left - this.map.paddingForPopups.right - wPadding - extraX; safeContentSize.w = Math.min(safeContentSize.w, maxX); safeContentSize.h = Math.min(safeContentSize.h, maxY); } return safeContentSize; }, /** * Method: getContentDivPadding * Glorious, oh glorious hack in order to determine the css 'padding' of * the contentDiv. IE/Opera return null here unless we actually add the * popup's main 'div' element (which contains contentDiv) to the DOM. * So we make it invisible and then add it to the document temporarily. * * Once we've taken the padding readings we need, we then remove it * from the DOM (it will actually get added to the DOM in * Map.js's addPopup) * * Returns: * {} */ getContentDivPadding: function() { //use cached value if we have it var contentDivPadding = this._contentDivPadding; if (!contentDivPadding) { if (this.div.parentNode == null) { //make the div invisible and add it to the page this.div.style.display = "none"; document.body.appendChild(this.div); } //read the padding settings from css, put them in an OL.Bounds contentDivPadding = new OpenLayers.Bounds( OpenLayers.Element.getStyle(this.contentDiv, "padding-left"), OpenLayers.Element.getStyle(this.contentDiv, "padding-bottom"), OpenLayers.Element.getStyle(this.contentDiv, "padding-right"), OpenLayers.Element.getStyle(this.contentDiv, "padding-top") ); //cache the value this._contentDivPadding = contentDivPadding; if (this.div.parentNode == document.body) { //remove the div from the page and make it visible again document.body.removeChild(this.div); this.div.style.display = ""; } } return contentDivPadding; }, /** * Method: addCloseBox * * Parameters: * callback - {Function} The callback to be called when the close button * is clicked. */ addCloseBox: function(callback) { this.closeDiv = OpenLayers.Util.createDiv( this.id + "_close", null, {w: 17, h: 17} ); this.closeDiv.className = "olPopupCloseBox"; // use the content div's css padding to determine if we should // padd the close div var contentDivPadding = this.getContentDivPadding(); this.closeDiv.style.right = contentDivPadding.right + "px"; this.closeDiv.style.top = contentDivPadding.top + "px"; this.groupDiv.appendChild(this.closeDiv); var closePopup = callback || function(e) { this.hide(); OpenLayers.Event.stop(e); }; OpenLayers.Event.observe(this.closeDiv, "touchend", OpenLayers.Function.bindAsEventListener(closePopup, this)); OpenLayers.Event.observe(this.closeDiv, "click", OpenLayers.Function.bindAsEventListener(closePopup, this)); }, /** * Method: panIntoView * Pans the map such that the popup is totaly viewable (if necessary) */ panIntoView: function() { var mapSize = this.map.getSize(); //start with the top left corner of the popup, in px, // relative to the viewport var origTL = this.map.getViewPortPxFromLayerPx( new OpenLayers.Pixel( parseInt(this.div.style.left), parseInt(this.div.style.top) )); var newTL = origTL.clone(); //new left (compare to margins, using this.size to calculate right) if (origTL.x < this.map.paddingForPopups.left) { newTL.x = this.map.paddingForPopups.left; } else if ( (origTL.x + this.size.w) > (mapSize.w - this.map.paddingForPopups.right)) { newTL.x = mapSize.w - this.map.paddingForPopups.right - this.size.w; } //new top (compare to margins, using this.size to calculate bottom) if (origTL.y < this.map.paddingForPopups.top) { newTL.y = this.map.paddingForPopups.top; } else if ( (origTL.y + this.size.h) > (mapSize.h - this.map.paddingForPopups.bottom)) { newTL.y = mapSize.h - this.map.paddingForPopups.bottom - this.size.h; } var dx = origTL.x - newTL.x; var dy = origTL.y - newTL.y; this.map.pan(dx, dy); }, /** * Method: registerEvents * Registers events on the popup. * * Do this in a separate function so that subclasses can * choose to override it if they wish to deal differently * with mouse events * * Note in the following handler functions that some special * care is needed to deal correctly with mousing and popups. * * Because the user might select the zoom-rectangle option and * then drag it over a popup, we need a safe way to allow the * mousemove and mouseup events to pass through the popup when * they are initiated from outside. The same procedure is needed for * touchmove and touchend events. * * Otherwise, we want to essentially kill the event propagation * for all other events, though we have to do so carefully, * without disabling basic html functionality, like clicking on * hyperlinks or drag-selecting text. */ registerEvents:function() { this.events = new OpenLayers.Events(this, this.div, null, true); function onTouchstart(evt) { OpenLayers.Event.stop(evt, true); } this.events.on({ "mousedown": this.onmousedown, "mousemove": this.onmousemove, "mouseup": this.onmouseup, "click": this.onclick, "mouseout": this.onmouseout, "dblclick": this.ondblclick, "touchstart": onTouchstart, scope: this }); }, /** * Method: onmousedown * When mouse goes down within the popup, make a note of * it locally, and then do not propagate the mousedown * (but do so safely so that user can select text inside) * * Parameters: * evt - {Event} */ onmousedown: function (evt) { this.mousedown = true; OpenLayers.Event.stop(evt, true); }, /** * Method: onmousemove * If the drag was started within the popup, then * do not propagate the mousemove (but do so safely * so that user can select text inside) * * Parameters: * evt - {Event} */ onmousemove: function (evt) { if (this.mousedown) { OpenLayers.Event.stop(evt, true); } }, /** * Method: onmouseup * When mouse comes up within the popup, after going down * in it, reset the flag, and then (once again) do not * propagate the event, but do so safely so that user can * select text inside * * Parameters: * evt - {Event} */ onmouseup: function (evt) { if (this.mousedown) { this.mousedown = false; OpenLayers.Event.stop(evt, true); } }, /** * Method: onclick * Ignore clicks, but allowing default browser handling * * Parameters: * evt - {Event} */ onclick: function (evt) { OpenLayers.Event.stop(evt, true); }, /** * Method: onmouseout * When mouse goes out of the popup set the flag to false so that * if they let go and then drag back in, we won't be confused. * * Parameters: * evt - {Event} */ onmouseout: function (evt) { this.mousedown = false; }, /** * Method: ondblclick * Ignore double-clicks, but allowing default browser handling * * Parameters: * evt - {Event} */ ondblclick: function (evt) { OpenLayers.Event.stop(evt, true); }, CLASS_NAME: "OpenLayers.Popup" }); OpenLayers.Popup.WIDTH = 200; OpenLayers.Popup.HEIGHT = 200; OpenLayers.Popup.COLOR = "white"; OpenLayers.Popup.OPACITY = 1; OpenLayers.Popup.BORDER = "0px"; /* ====================================================================== OpenLayers/Popup/Anchored.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of 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/Popup.js */ /** * Class: OpenLayers.Popup.Anchored * * Inherits from: * - */ OpenLayers.Popup.Anchored = OpenLayers.Class(OpenLayers.Popup, { /** * Property: relativePosition * {String} Relative position of the popup ("br", "tr", "tl" or "bl"). */ relativePosition: null, /** * APIProperty: keepInMap * {Boolean} If panMapIfOutOfView is false, and this property is true, * contrain the popup such that it always fits in the available map * space. By default, this is set. If you are creating popups that are * near map edges and not allowing pannning, and especially if you have * a popup which has a fixedRelativePosition, setting this to false may * be a smart thing to do. * * For anchored popups, default is true, since subclasses will * usually want this functionality. */ keepInMap: true, /** * Property: anchor * {Object} Object to which we'll anchor the popup. Must expose a * 'size' () and 'offset' (). */ anchor: null, /** * Constructor: OpenLayers.Popup.Anchored * * Parameters: * id - {String} * lonlat - {} * contentSize - {} * contentHTML - {String} * anchor - {Object} Object which must expose a 'size' * and 'offset' (generally an ). * closeBox - {Boolean} * closeBoxCallback - {Function} Function to be called on closeBox click. */ initialize:function(id, lonlat, contentSize, contentHTML, anchor, closeBox, closeBoxCallback) { var newArguments = [ id, lonlat, contentSize, contentHTML, closeBox, closeBoxCallback ]; OpenLayers.Popup.prototype.initialize.apply(this, newArguments); this.anchor = (anchor != null) ? anchor : { size: new OpenLayers.Size(0,0), offset: new OpenLayers.Pixel(0,0)}; }, /** * APIMethod: destroy */ destroy: function() { this.anchor = null; this.relativePosition = null; OpenLayers.Popup.prototype.destroy.apply(this, arguments); }, /** * APIMethod: show * Overridden from Popup since user might hide popup and then show() it * in a new location (meaning we might want to update the relative * position on the show) */ show: function() { this.updatePosition(); OpenLayers.Popup.prototype.show.apply(this, arguments); }, /** * Method: moveTo * Since the popup is moving to a new px, it might need also to be moved * relative to where the marker is. We first calculate the new * relativePosition, and then we calculate the new px where we will * put the popup, based on the new relative position. * * If the relativePosition has changed, we must also call * updateRelativePosition() to make any visual changes to the popup * which are associated with putting it in a new relativePosition. * * Parameters: * px - {} */ moveTo: function(px) { var oldRelativePosition = this.relativePosition; this.relativePosition = this.calculateRelativePosition(px); OpenLayers.Popup.prototype.moveTo.call(this, this.calculateNewPx(px)); //if this move has caused the popup to change its relative position, // we need to make the appropriate cosmetic changes. if (this.relativePosition != oldRelativePosition) { this.updateRelativePosition(); } }, /** * APIMethod: setSize * * Parameters: * contentSize - {} the new size for the popup's * contents div (in pixels). */ setSize:function(contentSize) { OpenLayers.Popup.prototype.setSize.apply(this, arguments); if ((this.lonlat) && (this.map)) { var px = this.map.getLayerPxFromLonLat(this.lonlat); this.moveTo(px); } }, /** * Method: calculateRelativePosition * * Parameters: * px - {} * * Returns: * {String} The relative position ("br" "tr" "tl" "bl") at which the popup * should be placed. */ calculateRelativePosition:function(px) { var lonlat = this.map.getLonLatFromLayerPx(px); var extent = this.map.getExtent(); var quadrant = extent.determineQuadrant(lonlat); return OpenLayers.Bounds.oppositeQuadrant(quadrant); }, /** * Method: updateRelativePosition * The popup has been moved to a new relative location, so we may want to * make some cosmetic adjustments to it. * * Note that in the classic Anchored popup, there is nothing to do * here, since the popup looks exactly the same in all four positions. * Subclasses such as Framed, however, will want to do something * special here. */ updateRelativePosition: function() { //to be overridden by subclasses }, /** * Method: calculateNewPx * * Parameters: * px - {} * * Returns: * {} The the new px position of the popup on the screen * relative to the passed-in px. */ calculateNewPx:function(px) { var newPx = px.offset(this.anchor.offset); //use contentSize if size is not already set var size = this.size || this.contentSize; var top = (this.relativePosition.charAt(0) == 't'); newPx.y += (top) ? -size.h : this.anchor.size.h; var left = (this.relativePosition.charAt(1) == 'l'); newPx.x += (left) ? -size.w : this.anchor.size.w; return newPx; }, CLASS_NAME: "OpenLayers.Popup.Anchored" }); /* ====================================================================== OpenLayers/Popup/Framed.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of 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/Popup/Anchored.js */ /** * Class: OpenLayers.Popup.Framed * * Inherits from: * - */ OpenLayers.Popup.Framed = OpenLayers.Class(OpenLayers.Popup.Anchored, { /** * Property: imageSrc * {String} location of the image to be used as the popup frame */ imageSrc: null, /** * Property: imageSize * {} Size (measured in pixels) of the image located * by the 'imageSrc' property. */ imageSize: null, /** * APIProperty: isAlphaImage * {Boolean} The image has some alpha and thus needs to use the alpha * image hack. Note that setting this to true will have no noticeable * effect in FF or IE7 browsers, but will all but crush the ie6 * browser. * Default is false. */ isAlphaImage: false, /** * Property: positionBlocks * {Object} Hash of different position blocks (Object/Hashs). Each block * will be keyed by a two-character 'relativePosition' * code string (ie "tl", "tr", "bl", "br"). Block properties are * 'offset', 'padding' (self-explanatory), and finally the 'blocks' * parameter, which is an array of the block objects. * * Each block object must have 'size', 'anchor', and 'position' * properties. * * Note that positionBlocks should never be modified at runtime. */ positionBlocks: null, /** * Property: blocks * {Array[Object]} Array of objects, each of which is one "block" of the * popup. Each block has a 'div' and an 'image' property, both of * which are DOMElements, and the latter of which is appended to the * former. These are reused as the popup goes changing positions for * great economy and elegance. */ blocks: null, /** * APIProperty: fixedRelativePosition * {Boolean} We want the framed popup to work dynamically placed relative * to its anchor but also in just one fixed position. A well designed * framed popup will have the pixels and logic to display itself in * any of the four relative positions, but (understandably), this will * not be the case for all of them. By setting this property to 'true', * framed popup will not recalculate for the best placement each time * it's open, but will always open the same way. * Note that if this is set to true, it is generally advisable to also * set the 'panIntoView' property to true so that the popup can be * scrolled into view (since it will often be offscreen on open) * Default is false. */ fixedRelativePosition: false, /** * Constructor: OpenLayers.Popup.Framed * * Parameters: * id - {String} * lonlat - {} * contentSize - {} * contentHTML - {String} * anchor - {Object} Object to which we'll anchor the popup. Must expose * a 'size' () and 'offset' () * (Note that this is generally an ). * closeBox - {Boolean} * closeBoxCallback - {Function} Function to be called on closeBox click. */ initialize:function(id, lonlat, contentSize, contentHTML, anchor, closeBox, closeBoxCallback) { OpenLayers.Popup.Anchored.prototype.initialize.apply(this, arguments); if (this.fixedRelativePosition) { //based on our decided relativePostion, set the current padding // this keeps us from getting into trouble this.updateRelativePosition(); //make calculateRelativePosition always return the specified // fixed position. this.calculateRelativePosition = function(px) { return this.relativePosition; }; } this.contentDiv.style.position = "absolute"; this.contentDiv.style.zIndex = 1; if (closeBox) { this.closeDiv.style.zIndex = 1; } this.groupDiv.style.position = "absolute"; this.groupDiv.style.top = "0px"; this.groupDiv.style.left = "0px"; this.groupDiv.style.height = "100%"; this.groupDiv.style.width = "100%"; }, /** * APIMethod: destroy */ destroy: function() { this.imageSrc = null; this.imageSize = null; this.isAlphaImage = null; this.fixedRelativePosition = false; this.positionBlocks = null; //remove our blocks for(var i = 0; i < this.blocks.length; i++) { var block = this.blocks[i]; if (block.image) { block.div.removeChild(block.image); } block.image = null; if (block.div) { this.groupDiv.removeChild(block.div); } block.div = null; } this.blocks = null; OpenLayers.Popup.Anchored.prototype.destroy.apply(this, arguments); }, /** * APIMethod: setBackgroundColor */ setBackgroundColor:function(color) { //does nothing since the framed popup's entire scheme is based on a // an image -- changing the background color makes no sense. }, /** * APIMethod: setBorder */ setBorder:function() { //does nothing since the framed popup's entire scheme is based on a // an image -- changing the popup's border makes no sense. }, /** * Method: setOpacity * Sets the opacity of the popup. * * Parameters: * opacity - {float} A value between 0.0 (transparent) and 1.0 (solid). */ setOpacity:function(opacity) { //does nothing since we suppose that we'll never apply an opacity // to a framed popup }, /** * APIMethod: setSize * Overridden here, because we need to update the blocks whenever the size * of the popup has changed. * * Parameters: * contentSize - {} the new size for the popup's * contents div (in pixels). */ setSize:function(contentSize) { OpenLayers.Popup.Anchored.prototype.setSize.apply(this, arguments); this.updateBlocks(); }, /** * Method: updateRelativePosition * When the relative position changes, we need to set the new padding * BBOX on the popup, reposition the close div, and update the blocks. */ updateRelativePosition: function() { //update the padding this.padding = this.positionBlocks[this.relativePosition].padding; //update the position of our close box to new padding if (this.closeDiv) { // use the content div's css padding to determine if we should // padd the close div var contentDivPadding = this.getContentDivPadding(); this.closeDiv.style.right = contentDivPadding.right + this.padding.right + "px"; this.closeDiv.style.top = contentDivPadding.top + this.padding.top + "px"; } this.updateBlocks(); }, /** * Method: calculateNewPx * Besides the standard offset as determined by the Anchored class, our * Framed popups have a special 'offset' property for each of their * positions, which is used to offset the popup relative to its anchor. * * Parameters: * px - {} * * Returns: * {} The the new px position of the popup on the screen * relative to the passed-in px. */ calculateNewPx:function(px) { var newPx = OpenLayers.Popup.Anchored.prototype.calculateNewPx.apply( this, arguments ); newPx = newPx.offset(this.positionBlocks[this.relativePosition].offset); return newPx; }, /** * Method: createBlocks */ createBlocks: function() { this.blocks = []; //since all positions contain the same number of blocks, we can // just pick the first position and use its blocks array to create // our blocks array var firstPosition = null; for(var key in this.positionBlocks) { firstPosition = key; break; } var position = this.positionBlocks[firstPosition]; for (var i = 0; i < position.blocks.length; i++) { var block = {}; this.blocks.push(block); var divId = this.id + '_FrameDecorationDiv_' + i; block.div = OpenLayers.Util.createDiv(divId, null, null, null, "absolute", null, "hidden", null ); var imgId = this.id + '_FrameDecorationImg_' + i; var imageCreator = (this.isAlphaImage) ? OpenLayers.Util.createAlphaImageDiv : OpenLayers.Util.createImage; block.image = imageCreator(imgId, null, this.imageSize, this.imageSrc, "absolute", null, null, null ); block.div.appendChild(block.image); this.groupDiv.appendChild(block.div); } }, /** * Method: updateBlocks * Internal method, called on initialize and when the popup's relative * position has changed. This function takes care of re-positioning * the popup's blocks in their appropropriate places. */ updateBlocks: function() { if (!this.blocks) { this.createBlocks(); } if (this.size && this.relativePosition) { var position = this.positionBlocks[this.relativePosition]; for (var i = 0; i < position.blocks.length; i++) { var positionBlock = position.blocks[i]; var block = this.blocks[i]; // adjust sizes var l = positionBlock.anchor.left; var b = positionBlock.anchor.bottom; var r = positionBlock.anchor.right; var t = positionBlock.anchor.top; //note that we use the isNaN() test here because if the // size object is initialized with a "auto" parameter, the // size constructor calls parseFloat() on the string, // which will turn it into NaN // var w = (isNaN(positionBlock.size.w)) ? this.size.w - (r + l) : positionBlock.size.w; var h = (isNaN(positionBlock.size.h)) ? this.size.h - (b + t) : positionBlock.size.h; block.div.style.width = (w < 0 ? 0 : w) + 'px'; block.div.style.height = (h < 0 ? 0 : h) + 'px'; block.div.style.left = (l != null) ? l + 'px' : ''; block.div.style.bottom = (b != null) ? b + 'px' : ''; block.div.style.right = (r != null) ? r + 'px' : ''; block.div.style.top = (t != null) ? t + 'px' : ''; block.image.style.left = positionBlock.position.x + 'px'; block.image.style.top = positionBlock.position.y + 'px'; } this.contentDiv.style.left = this.padding.left + "px"; this.contentDiv.style.top = this.padding.top + "px"; } }, CLASS_NAME: "OpenLayers.Popup.Framed" }); /* ====================================================================== OpenLayers/Format.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/BaseTypes/Class.js * @requires OpenLayers/Util.js */ /** * Class: OpenLayers.Format * Base class for format reading/writing a variety of formats. Subclasses * of OpenLayers.Format are expected to have read and write methods. */ OpenLayers.Format = OpenLayers.Class({ /** * Property: options * {Object} A reference to options passed to the constructor. */ options: null, /** * APIProperty: externalProjection * {} When passed a externalProjection and * internalProjection, the format will reproject the geometries it * reads or writes. The externalProjection is the projection used by * the content which is passed into read or which comes out of write. * In order to reproject, a projection transformation function for the * specified projections must be available. This support may be * provided via proj4js or via a custom transformation function. See * {} for more information on * custom transformations. */ externalProjection: null, /** * APIProperty: internalProjection * {} When passed a externalProjection and * internalProjection, the format will reproject the geometries it * reads or writes. The internalProjection is the projection used by * the geometries which are returned by read or which are passed into * write. In order to reproject, a projection transformation function * for the specified projections must be available. This support may be * provided via proj4js or via a custom transformation function. See * {} for more information on * custom transformations. */ internalProjection: null, /** * APIProperty: data * {Object} When is true, this is the parsed string sent to * . */ data: null, /** * APIProperty: keepData * {Object} Maintain a reference () to the most recently read data. * Default is false. */ keepData: false, /** * Constructor: OpenLayers.Format * Instances of this class are not useful. See one of the subclasses. * * Parameters: * options - {Object} An optional object with properties to set on the * format * * Valid options: * keepData - {Boolean} If true, upon , the data property will be * set to the parsed object (e.g. the json or xml object). * * Returns: * An instance of OpenLayers.Format */ initialize: function(options) { OpenLayers.Util.extend(this, options); this.options = options; }, /** * APIMethod: destroy * Clean up. */ destroy: function() { }, /** * Method: read * Read data from a string, and return an object whose type depends on the * subclass. * * Parameters: * data - {string} Data to read/parse. * * Returns: * Depends on the subclass */ read: function(data) { throw new Error('Read not implemented.'); }, /** * Method: write * Accept an object, and return a string. * * Parameters: * object - {Object} Object to be serialized * * Returns: * {String} A string representation of the object. */ write: function(object) { throw new Error('Write not implemented.'); }, CLASS_NAME: "OpenLayers.Format" }); /* ====================================================================== OpenLayers/Format/JSON.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * Note: * This work draws heavily from the public domain JSON serializer/deserializer * at http://www.json.org/json.js. Rewritten so that it doesn't modify * basic data prototypes. */ /** * @requires OpenLayers/Format.js */ /** * Class: OpenLayers.Format.JSON * A parser to read/write JSON safely. Create a new instance with the * constructor. * * Inherits from: * - */ OpenLayers.Format.JSON = OpenLayers.Class(OpenLayers.Format, { /** * APIProperty: indent * {String} For "pretty" printing, the indent string will be used once for * each indentation level. */ indent: " ", /** * APIProperty: space * {String} For "pretty" printing, the space string will be used after * the ":" separating a name/value pair. */ space: " ", /** * APIProperty: newline * {String} For "pretty" printing, the newline string will be used at the * end of each name/value pair or array item. */ newline: "\n", /** * Property: level * {Integer} For "pretty" printing, this is incremented/decremented during * serialization. */ level: 0, /** * Property: pretty * {Boolean} Serialize with extra whitespace for structure. This is set * by the method. */ pretty: false, /** * Property: nativeJSON * {Boolean} Does the browser support native json? */ nativeJSON: (function() { return !!(window.JSON && typeof JSON.parse == "function" && typeof JSON.stringify == "function"); })(), /** * Constructor: OpenLayers.Format.JSON * Create a new parser for JSON. * * Parameters: * options - {Object} An optional object whose properties will be set on * this instance. */ /** * APIMethod: read * Deserialize a json string. * * Parameters: * json - {String} A JSON string * filter - {Function} A function which will be called for every key and * value at every level of the final result. Each value will be * replaced by the result of the filter function. This can be used to * reform generic objects into instances of classes, or to transform * date strings into Date objects. * * Returns: * {Object} An object, array, string, or number . */ read: function(json, filter) { var object; if (this.nativeJSON) { object = JSON.parse(json, filter); } else try { /** * Parsing happens in three stages. In the first stage, we run the * text against a regular expression which looks for non-JSON * characters. We are especially concerned with '()' and 'new' * because they can cause invocation, and '=' because it can * cause mutation. But just to be safe, we will reject all * unexpected characters. */ if (/^[\],:{}\s]*$/.test(json.replace(/\\["\\\/bfnrtu]/g, '@'). replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { /** * In the second stage we use the eval function to compile the * text into a JavaScript structure. The '{' operator is * subject to a syntactic ambiguity in JavaScript - it can * begin a block or an object literal. We wrap the text in * parens to eliminate the ambiguity. */ object = eval('(' + json + ')'); /** * In the optional third stage, we recursively walk the new * structure, passing each name/value pair to a filter * function for possible transformation. */ if(typeof filter === 'function') { function walk(k, v) { if(v && typeof v === 'object') { for(var i in v) { if(v.hasOwnProperty(i)) { v[i] = walk(i, v[i]); } } } return filter(k, v); } object = walk('', object); } } } catch(e) { // Fall through if the regexp test fails. } if(this.keepData) { this.data = object; } return object; }, /** * APIMethod: write * Serialize an object into a JSON string. * * Parameters: * value - {String} The object, array, string, number, boolean or date * to be serialized. * pretty - {Boolean} Structure the output with newlines and indentation. * Default is false. * * Returns: * {String} The JSON string representation of the input value. */ write: function(value, pretty) { this.pretty = !!pretty; var json = null; var type = typeof value; if(this.serialize[type]) { try { json = (!this.pretty && this.nativeJSON) ? JSON.stringify(value) : this.serialize[type].apply(this, [value]); } catch(err) { OpenLayers.Console.error("Trouble serializing: " + err); } } return json; }, /** * Method: writeIndent * Output an indentation string depending on the indentation level. * * Returns: * {String} An appropriate indentation string. */ writeIndent: function() { var pieces = []; if(this.pretty) { for(var i=0; i 0) { pieces.push(','); } pieces.push(this.writeNewline(), this.writeIndent(), json); } } this.level -= 1; pieces.push(this.writeNewline(), this.writeIndent(), ']'); return pieces.join(''); }, /** * Method: serialize.string * Transform a string into a JSON string. * * Parameters: * string - {String} The string to be serialized * * Returns: * {String} A JSON string representing the string. */ 'string': function(string) { // If the string contains no control characters, no quote characters, and no // backslash characters, then we can simply slap some quotes around it. // Otherwise we must also replace the offending characters with safe // sequences. var m = { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\' }; if(/["\\\x00-\x1f]/.test(string)) { return '"' + string.replace(/([\x00-\x1f\\"])/g, function(a, b) { var c = m[b]; if(c) { return c; } c = b.charCodeAt(); return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16); }) + '"'; } return '"' + string + '"'; }, /** * Method: serialize.number * Transform a number into a JSON string. * * Parameters: * number - {Number} The number to be serialized. * * Returns: * {String} A JSON string representing the number. */ 'number': function(number) { return isFinite(number) ? String(number) : "null"; }, /** * Method: serialize.boolean * Transform a boolean into a JSON string. * * Parameters: * bool - {Boolean} The boolean to be serialized. * * Returns: * {String} A JSON string representing the boolean. */ 'boolean': function(bool) { return String(bool); }, /** * Method: serialize.object * Transform a date into a JSON string. * * Parameters: * date - {Date} The date to be serialized. * * Returns: * {String} A JSON string representing the date. */ 'date': function(date) { function format(number) { // Format integers to have at least two digits. return (number < 10) ? '0' + number : number; } return '"' + date.getFullYear() + '-' + format(date.getMonth() + 1) + '-' + format(date.getDate()) + 'T' + format(date.getHours()) + ':' + format(date.getMinutes()) + ':' + format(date.getSeconds()) + '"'; } }, CLASS_NAME: "OpenLayers.Format.JSON" }); /* ====================================================================== OpenLayers/Format/GeoJSON.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Format/JSON.js * @requires OpenLayers/Feature/Vector.js * @requires OpenLayers/Geometry/Point.js * @requires OpenLayers/Geometry/MultiPoint.js * @requires OpenLayers/Geometry/LineString.js * @requires OpenLayers/Geometry/MultiLineString.js * @requires OpenLayers/Geometry/Polygon.js * @requires OpenLayers/Geometry/MultiPolygon.js * @requires OpenLayers/Console.js */ /** * Class: OpenLayers.Format.GeoJSON * Read and write GeoJSON. Create a new parser with the * constructor. * * Inherits from: * - */ OpenLayers.Format.GeoJSON = OpenLayers.Class(OpenLayers.Format.JSON, { /** * APIProperty: ignoreExtraDims * {Boolean} Ignore dimensions higher than 2 when reading geometry * coordinates. */ ignoreExtraDims: false, /** * Constructor: OpenLayers.Format.GeoJSON * Create a new parser for GeoJSON. * * Parameters: * options - {Object} An optional object whose properties will be set on * this instance. */ /** * APIMethod: read * Deserialize a GeoJSON string. * * Parameters: * json - {String} A GeoJSON string * type - {String} Optional string that determines the structure of * the output. Supported values are "Geometry", "Feature", and * "FeatureCollection". If absent or null, a default of * "FeatureCollection" is assumed. * filter - {Function} A function which will be called for every key and * value at every level of the final result. Each value will be * replaced by the result of the filter function. This can be used to * reform generic objects into instances of classes, or to transform * date strings into Date objects. * * Returns: * {Object} The return depends on the value of the type argument. If type * is "FeatureCollection" (the default), the return will be an array * of . If type is "Geometry", the input json * must represent a single geometry, and the return will be an * . If type is "Feature", the input json must * represent a single feature, and the return will be an * . */ read: function(json, type, filter) { type = (type) ? type : "FeatureCollection"; var results = null; var obj = null; if (typeof json == "string") { obj = OpenLayers.Format.JSON.prototype.read.apply(this, [json, filter]); } else { obj = json; } if(!obj) { OpenLayers.Console.error("Bad JSON: " + json); } else if(typeof(obj.type) != "string") { OpenLayers.Console.error("Bad GeoJSON - no type: " + json); } else if(this.isValidType(obj, type)) { switch(type) { case "Geometry": try { results = this.parseGeometry(obj); } catch(err) { OpenLayers.Console.error(err); } break; case "Feature": try { results = this.parseFeature(obj); results.type = "Feature"; } catch(err) { OpenLayers.Console.error(err); } break; case "FeatureCollection": // for type FeatureCollection, we allow input to be any type results = []; switch(obj.type) { case "Feature": try { results.push(this.parseFeature(obj)); } catch(err) { results = null; OpenLayers.Console.error(err); } break; case "FeatureCollection": for(var i=0, len=obj.features.length; i. * * Parameters: * obj - {Object} An object created from a GeoJSON object * * Returns: * {} A feature. */ parseFeature: function(obj) { var feature, geometry, attributes, bbox; attributes = (obj.properties) ? obj.properties : {}; bbox = (obj.geometry && obj.geometry.bbox) || obj.bbox; try { geometry = this.parseGeometry(obj.geometry); } catch(err) { // deal with bad geometries throw err; } feature = new OpenLayers.Feature.Vector(geometry, attributes); if(bbox) { feature.bounds = OpenLayers.Bounds.fromArray(bbox); } if(obj.id) { feature.fid = obj.id; } return feature; }, /** * Method: parseGeometry * Convert a geometry object from GeoJSON into an . * * Parameters: * obj - {Object} An object created from a GeoJSON object * * Returns: * {} A geometry. */ parseGeometry: function(obj) { if (obj == null) { return null; } var geometry, collection = false; if(obj.type == "GeometryCollection") { if(!(OpenLayers.Util.isArray(obj.geometries))) { throw "GeometryCollection must have geometries array: " + obj; } var numGeom = obj.geometries.length; var components = new Array(numGeom); for(var i=0; i. * * Parameters: * array - {Object} The coordinates array from the GeoJSON fragment. * * Returns: * {} A geometry. */ "point": function(array) { if (this.ignoreExtraDims == false && array.length != 2) { throw "Only 2D points are supported: " + array; } return new OpenLayers.Geometry.Point(array[0], array[1]); }, /** * Method: parseCoords.multipoint * Convert a coordinate array from GeoJSON into an * . * * Parameters: * array - {Object} The coordinates array from the GeoJSON fragment. * * Returns: * {} A geometry. */ "multipoint": function(array) { var points = []; var p = null; for(var i=0, len=array.length; i. * * Parameters: * array - {Object} The coordinates array from the GeoJSON fragment. * * Returns: * {} A geometry. */ "linestring": function(array) { var points = []; var p = null; for(var i=0, len=array.length; i. * * Parameters: * array - {Object} The coordinates array from the GeoJSON fragment. * * Returns: * {} A geometry. */ "multilinestring": function(array) { var lines = []; var l = null; for(var i=0, len=array.length; i. * * Returns: * {} A geometry. */ "polygon": function(array) { var rings = []; var r, l; for(var i=0, len=array.length; i. * * Parameters: * array - {Object} The coordinates array from the GeoJSON fragment. * * Returns: * {} A geometry. */ "multipolygon": function(array) { var polys = []; var p = null; for(var i=0, len=array.length; i. * * Parameters: * array - {Object} The coordinates array from the GeoJSON fragment. * * Returns: * {} A geometry. */ "box": function(array) { if(array.length != 2) { throw "GeoJSON box coordinates must have 2 elements"; } return new OpenLayers.Geometry.Polygon([ new OpenLayers.Geometry.LinearRing([ new OpenLayers.Geometry.Point(array[0][0], array[0][1]), new OpenLayers.Geometry.Point(array[1][0], array[0][1]), new OpenLayers.Geometry.Point(array[1][0], array[1][1]), new OpenLayers.Geometry.Point(array[0][0], array[1][1]), new OpenLayers.Geometry.Point(array[0][0], array[0][1]) ]) ]); } }, /** * APIMethod: write * Serialize a feature, geometry, array of features into a GeoJSON string. * * Parameters: * obj - {Object} An , , * or an array of features. * pretty - {Boolean} Structure the output with newlines and indentation. * Default is false. * * Returns: * {String} The GeoJSON string representation of the input geometry, * features, or array of features. */ write: function(obj, pretty) { var geojson = { "type": null }; if(OpenLayers.Util.isArray(obj)) { geojson.type = "FeatureCollection"; var numFeatures = obj.length; geojson.features = new Array(numFeatures); for(var i=0; i} * * Returns: * {Object} An object which can be assigned to the crs property * of a GeoJSON object. */ createCRSObject: function(object) { var proj = object.layer.projection.toString(); var crs = {}; if (proj.match(/epsg:/i)) { var code = parseInt(proj.substring(proj.indexOf(":") + 1)); if (code == 4326) { crs = { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }; } else { crs = { "type": "name", "properties": { "name": "EPSG:" + code } }; } } return crs; }, /** * Property: extract * Object with properties corresponding to the GeoJSON types. * Property values are functions that do the actual value extraction. */ extract: { /** * Method: extract.feature * Return a partial GeoJSON object representing a single feature. * * Parameters: * feature - {} * * Returns: * {Object} An object representing the point. */ 'feature': function(feature) { var geom = this.extract.geometry.apply(this, [feature.geometry]); var json = { "type": "Feature", "properties": feature.attributes, "geometry": geom }; if (feature.fid != null) { json.id = feature.fid; } return json; }, /** * Method: extract.geometry * Return a GeoJSON object representing a single geometry. * * Parameters: * geometry - {} * * Returns: * {Object} An object representing the geometry. */ 'geometry': function(geometry) { if (geometry == null) { return null; } if (this.internalProjection && this.externalProjection) { geometry = geometry.clone(); geometry.transform(this.internalProjection, this.externalProjection); } var geometryType = geometry.CLASS_NAME.split('.')[2]; var data = this.extract[geometryType.toLowerCase()].apply(this, [geometry]); var json; if(geometryType == "Collection") { json = { "type": "GeometryCollection", "geometries": data }; } else { json = { "type": geometryType, "coordinates": data }; } return json; }, /** * Method: extract.point * Return an array of coordinates from a point. * * Parameters: * point - {} * * Returns: * {Array} An array of coordinates representing the point. */ 'point': function(point) { return [point.x, point.y]; }, /** * Method: extract.multipoint * Return an array of point coordinates from a multipoint. * * Parameters: * multipoint - {} * * Returns: * {Array} An array of point coordinate arrays representing * the multipoint. */ 'multipoint': function(multipoint) { var array = []; for(var i=0, len=multipoint.components.length; i} * * Returns: * {Array} An array of coordinate arrays representing * the linestring. */ 'linestring': function(linestring) { var array = []; for(var i=0, len=linestring.components.length; i} * * Returns: * {Array} An array of linestring arrays representing * the multilinestring. */ 'multilinestring': function(multilinestring) { var array = []; for(var i=0, len=multilinestring.components.length; i} * * Returns: * {Array} An array of linear ring arrays representing the polygon. */ 'polygon': function(polygon) { var array = []; for(var i=0, len=polygon.components.length; i} * * Returns: * {Array} An array of polygon arrays representing * the multipolygon */ 'multipolygon': function(multipolygon) { var array = []; for(var i=0, len=multipolygon.components.length; i} * * Returns: * {Array} An array of geometry objects representing the geometry * collection. */ 'collection': function(collection) { var len = collection.components.length; var array = new Array(len); for(var i=0; i=0; --i) { layer = layers[i]; if (layer instanceof OpenLayers.Layer.Google && layer.visibility === true && layer.inRange === true) { type = layer.type; visible = true; break; } } var container = this.mapObject.getDiv(); if (visible === true) { if (container.parentNode !== map.div) { if (!cache.rendered) { var me = this; google.maps.event.addListenerOnce(this.mapObject, 'tilesloaded', function() { cache.rendered = true; me.setGMapVisibility(me.getVisibility()); me.moveTo(me.map.getCenter()); }); } else { map.div.appendChild(container); cache.googleControl.appendChild(map.viewPortDiv); google.maps.event.trigger(this.mapObject, 'resize'); } } this.mapObject.setMapTypeId(type); } else if (cache.googleControl.hasChildNodes()) { map.div.appendChild(map.viewPortDiv); map.div.removeChild(container); } } }, /** * Method: getMapContainer * * Returns: * {DOMElement} the GMap container's div */ getMapContainer: function() { return this.mapObject.getDiv(); }, // // TRANSLATION: MapObject Bounds <-> OpenLayers.Bounds // /** * APIMethod: getMapObjectBoundsFromOLBounds * * Parameters: * olBounds - {} * * Returns: * {Object} A MapObject Bounds, translated from olBounds * Returns null if null value is passed in */ getMapObjectBoundsFromOLBounds: function(olBounds) { var moBounds = null; if (olBounds != null) { var sw = this.sphericalMercator ? this.inverseMercator(olBounds.bottom, olBounds.left) : new OpenLayers.LonLat(olBounds.bottom, olBounds.left); var ne = this.sphericalMercator ? this.inverseMercator(olBounds.top, olBounds.right) : new OpenLayers.LonLat(olBounds.top, olBounds.right); moBounds = new google.maps.LatLngBounds( new google.maps.LatLng(sw.lat, sw.lon), new google.maps.LatLng(ne.lat, ne.lon) ); } return moBounds; }, /************************************ * * * MapObject Interface Controls * * * ************************************/ // LonLat - Pixel Translation /** * APIMethod: getMapObjectLonLatFromMapObjectPixel * * Parameters: * moPixel - {Object} MapObject Pixel format * * Returns: * {Object} MapObject LonLat translated from MapObject Pixel */ getMapObjectLonLatFromMapObjectPixel: function(moPixel) { var size = this.map.getSize(); var lon = this.getLongitudeFromMapObjectLonLat(this.mapObject.center); var lat = this.getLatitudeFromMapObjectLonLat(this.mapObject.center); var res = this.map.getResolution(); var delta_x = moPixel.x - (size.w / 2); var delta_y = moPixel.y - (size.h / 2); var lonlat = new OpenLayers.LonLat( lon + delta_x * res, lat - delta_y * res ); if (this.wrapDateLine) { lonlat = lonlat.wrapDateLine(this.maxExtent); } return this.getMapObjectLonLatFromLonLat(lonlat.lon, lonlat.lat); }, /** * APIMethod: getMapObjectPixelFromMapObjectLonLat * * Parameters: * moLonLat - {Object} MapObject LonLat format * * Returns: * {Object} MapObject Pixel transtlated from MapObject LonLat */ getMapObjectPixelFromMapObjectLonLat: function(moLonLat) { var lon = this.getLongitudeFromMapObjectLonLat(moLonLat); var lat = this.getLatitudeFromMapObjectLonLat(moLonLat); var res = this.map.getResolution(); var extent = this.map.getExtent(); return this.getMapObjectPixelFromXY((1/res * (lon - extent.left)), (1/res * (extent.top - lat))); }, /** * APIMethod: setMapObjectCenter * Set the mapObject to the specified center and zoom * * Parameters: * center - {Object} MapObject LonLat format * zoom - {int} MapObject zoom format */ setMapObjectCenter: function(center, zoom) { if (this.animationEnabled === false && zoom != this.mapObject.zoom) { var mapContainer = this.getMapContainer(); google.maps.event.addListenerOnce( this.mapObject, "idle", function() { mapContainer.style.visibility = ""; } ); mapContainer.style.visibility = "hidden"; } this.mapObject.setOptions({ center: center, zoom: zoom }); }, // Bounds /** * APIMethod: getMapObjectZoomFromMapObjectBounds * * Parameters: * moBounds - {Object} MapObject Bounds format * * Returns: * {Object} MapObject Zoom for specified MapObject Bounds */ getMapObjectZoomFromMapObjectBounds: function(moBounds) { return this.mapObject.getBoundsZoomLevel(moBounds); }, /************************************ * * * MapObject Primitives * * * ************************************/ // LonLat /** * APIMethod: getMapObjectLonLatFromLonLat * * Parameters: * lon - {Float} * lat - {Float} * * Returns: * {Object} MapObject LonLat built from lon and lat params */ getMapObjectLonLatFromLonLat: function(lon, lat) { var gLatLng; if(this.sphericalMercator) { var lonlat = this.inverseMercator(lon, lat); gLatLng = new google.maps.LatLng(lonlat.lat, lonlat.lon); } else { gLatLng = new google.maps.LatLng(lat, lon); } return gLatLng; }, // Pixel /** * APIMethod: getMapObjectPixelFromXY * * Parameters: * x - {Integer} * y - {Integer} * * Returns: * {Object} MapObject Pixel from x and y parameters */ getMapObjectPixelFromXY: function(x, y) { return new google.maps.Point(x, y); } }; /* ====================================================================== OpenLayers/Request.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Events.js * @requires OpenLayers/Request/XMLHttpRequest.js */ /** * TODO: deprecate me * Use OpenLayers.Request.proxy instead. */ OpenLayers.ProxyHost = ""; /** * Namespace: OpenLayers.Request * The OpenLayers.Request namespace contains convenience methods for working * with XMLHttpRequests. These methods work with a cross-browser * W3C compliant class. */ if (!OpenLayers.Request) { /** * This allows for OpenLayers/Request/XMLHttpRequest.js to be included * before or after this script. */ OpenLayers.Request = {}; } OpenLayers.Util.extend(OpenLayers.Request, { /** * Constant: DEFAULT_CONFIG * {Object} Default configuration for all requests. */ DEFAULT_CONFIG: { method: "GET", url: window.location.href, async: true, user: undefined, password: undefined, params: null, proxy: OpenLayers.ProxyHost, headers: {}, data: null, callback: function() {}, success: null, failure: null, scope: null }, /** * Constant: URL_SPLIT_REGEX */ URL_SPLIT_REGEX: /([^:]*:)\/\/([^:]*:?[^@]*@)?([^:\/\?]*):?([^\/\?]*)/, /** * APIProperty: events * {} An events object that handles all * events on the {} object. * * All event listeners will receive an event object with three properties: * request - {} The request object. * config - {Object} The config object sent to the specific request method. * requestUrl - {String} The request url. * * Supported event types: * complete - Triggered when we have a response from the request, if a * listener returns false, no further response processing will take * place. * success - Triggered when the HTTP response has a success code (200-299). * failure - Triggered when the HTTP response does not have a success code. */ events: new OpenLayers.Events(this), /** * Method: makeSameOrigin * Using the specified proxy, returns a same origin url of the provided url. * * Parameters: * url - {String} An arbitrary url * proxy {String|Function} The proxy to use to make the provided url a * same origin url. * * Returns * {String} the same origin url. If no proxy is provided, the returned url * will be the same as the provided url. */ makeSameOrigin: function(url, proxy) { var sameOrigin = url.indexOf("http") !== 0; var urlParts = !sameOrigin && url.match(this.URL_SPLIT_REGEX); if (urlParts) { var location = window.location; sameOrigin = urlParts[1] == location.protocol && urlParts[3] == location.hostname; var uPort = urlParts[4], lPort = location.port; if (uPort != 80 && uPort != "" || lPort != "80" && lPort != "") { sameOrigin = sameOrigin && uPort == lPort; } } if (!sameOrigin) { if (proxy) { if (typeof proxy == "function") { url = proxy(url); } else { url = proxy + encodeURIComponent(url); } } } return url; }, /** * APIMethod: issue * Create a new XMLHttpRequest object, open it, set any headers, bind * a callback to done state, and send any data. It is recommended that * you use one , , , , , or . * This method is only documented to provide detail on the configuration * options available to all request methods. * * Parameters: * config - {Object} Object containing properties for configuring the * request. Allowed configuration properties are described below. * This object is modified and should not be reused. * * Allowed config properties: * method - {String} One of GET, POST, PUT, DELETE, HEAD, or * OPTIONS. Default is GET. * url - {String} URL for the request. * async - {Boolean} Open an asynchronous request. Default is true. * user - {String} User for relevant authentication scheme. Set * to null to clear current user. * password - {String} Password for relevant authentication scheme. * Set to null to clear current password. * proxy - {String} Optional proxy. Defaults to * . * params - {Object} Any key:value pairs to be appended to the * url as a query string. Assumes url doesn't already include a query * string or hash. Typically, this is only appropriate for * requests where the query string will be appended to the url. * Parameter values that are arrays will be * concatenated with a comma (note that this goes against form-encoding) * as is done with . * headers - {Object} Object with header:value pairs to be set on * the request. * data - {String | Document} Optional data to send with the request. * Typically, this is only used with and requests. * Make sure to provide the appropriate "Content-Type" header for your * data. For and requests, the content type defaults to * "application-xml". If your data is a different content type, or * if you are using a different HTTP method, set the "Content-Type" * header to match your data type. * callback - {Function} Function to call when request is done. * To determine if the request failed, check request.status (200 * indicates success). * success - {Function} Optional function to call if request status is in * the 200s. This will be called in addition to callback above and * would typically only be used as an alternative. * failure - {Function} Optional function to call if request status is not * in the 200s. This will be called in addition to callback above and * would typically only be used as an alternative. * scope - {Object} If callback is a public method on some object, * set the scope to that object. * * Returns: * {XMLHttpRequest} Request object. To abort the request before a response * is received, call abort() on the request object. */ issue: function(config) { // apply default config - proxy host may have changed var defaultConfig = OpenLayers.Util.extend( this.DEFAULT_CONFIG, {proxy: OpenLayers.ProxyHost} ); config = config || {}; config.headers = config.headers || {}; config = OpenLayers.Util.applyDefaults(config, defaultConfig); config.headers = OpenLayers.Util.applyDefaults(config.headers, defaultConfig.headers); // Always set the "X-Requested-With" header to signal that this request // was issued through the XHR-object. Since header keys are case // insensitive and we want to allow overriding of the "X-Requested-With" // header through the user we cannot use applyDefaults, but have to // check manually whether we were called with a "X-Requested-With" // header. var customRequestedWithHeader = false, headerKey; for(headerKey in config.headers) { if (config.headers.hasOwnProperty( headerKey )) { if (headerKey.toLowerCase() === 'x-requested-with') { customRequestedWithHeader = true; } } } if (customRequestedWithHeader === false) { // we did not have a custom "X-Requested-With" header config.headers['X-Requested-With'] = 'XMLHttpRequest'; } // create request, open, and set headers var request = new OpenLayers.Request.XMLHttpRequest(); var url = OpenLayers.Util.urlAppend(config.url, OpenLayers.Util.getParameterString(config.params || {})); url = OpenLayers.Request.makeSameOrigin(url, config.proxy); request.open( config.method, url, config.async, config.user, config.password ); for(var header in config.headers) { request.setRequestHeader(header, config.headers[header]); } var events = this.events; // we want to execute runCallbacks with "this" as the // execution scope var self = this; request.onreadystatechange = function() { if(request.readyState == OpenLayers.Request.XMLHttpRequest.DONE) { var proceed = events.triggerEvent( "complete", {request: request, config: config, requestUrl: url} ); if(proceed !== false) { self.runCallbacks( {request: request, config: config, requestUrl: url} ); } } }; // send request (optionally with data) and return // call in a timeout for asynchronous requests so the return is // available before readyState == 4 for cached docs if(config.async === false) { request.send(config.data); } else { window.setTimeout(function(){ if (request.readyState !== 0) { // W3C: 0-UNSENT request.send(config.data); } }, 0); } return request; }, /** * Method: runCallbacks * Calls the complete, success and failure callbacks. Application * can listen to the "complete" event, have the listener * display a confirm window and always return false, and * execute OpenLayers.Request.runCallbacks if the user * hits "yes" in the confirm window. * * Parameters: * options - {Object} Hash containing request, config and requestUrl keys */ runCallbacks: function(options) { var request = options.request; var config = options.config; // bind callbacks to readyState 4 (done) var complete = (config.scope) ? OpenLayers.Function.bind(config.callback, config.scope) : config.callback; // optional success callback var success; if(config.success) { success = (config.scope) ? OpenLayers.Function.bind(config.success, config.scope) : config.success; } // optional failure callback var failure; if(config.failure) { failure = (config.scope) ? OpenLayers.Function.bind(config.failure, config.scope) : config.failure; } if (OpenLayers.Util.createUrlObject(config.url).protocol == "file:" && request.responseText) { request.status = 200; } complete(request); if (!request.status || (request.status >= 200 && request.status < 300)) { this.events.triggerEvent("success", options); if(success) { success(request); } } if(request.status && (request.status < 200 || request.status >= 300)) { this.events.triggerEvent("failure", options); if(failure) { failure(request); } } }, /** * APIMethod: GET * Send an HTTP GET request. Additional configuration properties are * documented in the method, with the method property set * to GET. * * Parameters: * config - {Object} Object with properties for configuring the request. * See the method for documentation of allowed properties. * This object is modified and should not be reused. * * Returns: * {XMLHttpRequest} Request object. */ GET: function(config) { config = OpenLayers.Util.extend(config, {method: "GET"}); return OpenLayers.Request.issue(config); }, /** * APIMethod: POST * Send a POST request. Additional configuration properties are * documented in the method, with the method property set * to POST and "Content-Type" header set to "application/xml". * * Parameters: * config - {Object} Object with properties for configuring the request. * See the method for documentation of allowed properties. The * default "Content-Type" header will be set to "application-xml" if * none is provided. This object is modified and should not be reused. * * Returns: * {XMLHttpRequest} Request object. */ POST: function(config) { config = OpenLayers.Util.extend(config, {method: "POST"}); // set content type to application/xml if it isn't already set config.headers = config.headers ? config.headers : {}; if(!("CONTENT-TYPE" in OpenLayers.Util.upperCaseObject(config.headers))) { config.headers["Content-Type"] = "application/xml"; } return OpenLayers.Request.issue(config); }, /** * APIMethod: PUT * Send an HTTP PUT request. Additional configuration properties are * documented in the method, with the method property set * to PUT and "Content-Type" header set to "application/xml". * * Parameters: * config - {Object} Object with properties for configuring the request. * See the method for documentation of allowed properties. The * default "Content-Type" header will be set to "application-xml" if * none is provided. This object is modified and should not be reused. * * Returns: * {XMLHttpRequest} Request object. */ PUT: function(config) { config = OpenLayers.Util.extend(config, {method: "PUT"}); // set content type to application/xml if it isn't already set config.headers = config.headers ? config.headers : {}; if(!("CONTENT-TYPE" in OpenLayers.Util.upperCaseObject(config.headers))) { config.headers["Content-Type"] = "application/xml"; } return OpenLayers.Request.issue(config); }, /** * APIMethod: DELETE * Send an HTTP DELETE request. Additional configuration properties are * documented in the method, with the method property set * to DELETE. * * Parameters: * config - {Object} Object with properties for configuring the request. * See the method for documentation of allowed properties. * This object is modified and should not be reused. * * Returns: * {XMLHttpRequest} Request object. */ DELETE: function(config) { config = OpenLayers.Util.extend(config, {method: "DELETE"}); return OpenLayers.Request.issue(config); }, /** * APIMethod: HEAD * Send an HTTP HEAD request. Additional configuration properties are * documented in the method, with the method property set * to HEAD. * * Parameters: * config - {Object} Object with properties for configuring the request. * See the method for documentation of allowed properties. * This object is modified and should not be reused. * * Returns: * {XMLHttpRequest} Request object. */ HEAD: function(config) { config = OpenLayers.Util.extend(config, {method: "HEAD"}); return OpenLayers.Request.issue(config); }, /** * APIMethod: OPTIONS * Send an HTTP OPTIONS request. Additional configuration properties are * documented in the method, with the method property set * to OPTIONS. * * Parameters: * config - {Object} Object with properties for configuring the request. * See the method for documentation of allowed properties. * This object is modified and should not be reused. * * Returns: * {XMLHttpRequest} Request object. */ OPTIONS: function(config) { config = OpenLayers.Util.extend(config, {method: "OPTIONS"}); return OpenLayers.Request.issue(config); } }); /* ====================================================================== OpenLayers/Request/XMLHttpRequest.js ====================================================================== */ // XMLHttpRequest.js Copyright (C) 2010 Sergey Ilinsky (http://www.ilinsky.com) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * @requires OpenLayers/Request.js */ (function () { // Save reference to earlier defined object implementation (if any) var oXMLHttpRequest = window.XMLHttpRequest; // Define on browser type var bGecko = !!window.controllers, bIE = window.document.all && !window.opera, bIE7 = bIE && window.navigator.userAgent.match(/MSIE 7.0/); // Enables "XMLHttpRequest()" call next to "new XMLHttpReques()" function fXMLHttpRequest() { this._object = oXMLHttpRequest && !bIE7 ? new oXMLHttpRequest : new window.ActiveXObject("Microsoft.XMLHTTP"); this._listeners = []; }; // Constructor function cXMLHttpRequest() { return new fXMLHttpRequest; }; cXMLHttpRequest.prototype = fXMLHttpRequest.prototype; // BUGFIX: Firefox with Firebug installed would break pages if not executed if (bGecko && oXMLHttpRequest.wrapped) cXMLHttpRequest.wrapped = oXMLHttpRequest.wrapped; // Constants cXMLHttpRequest.UNSENT = 0; cXMLHttpRequest.OPENED = 1; cXMLHttpRequest.HEADERS_RECEIVED = 2; cXMLHttpRequest.LOADING = 3; cXMLHttpRequest.DONE = 4; // Public Properties cXMLHttpRequest.prototype.readyState = cXMLHttpRequest.UNSENT; cXMLHttpRequest.prototype.responseText = ''; cXMLHttpRequest.prototype.responseXML = null; cXMLHttpRequest.prototype.status = 0; cXMLHttpRequest.prototype.statusText = ''; // Priority proposal cXMLHttpRequest.prototype.priority = "NORMAL"; // Instance-level Events Handlers cXMLHttpRequest.prototype.onreadystatechange = null; // Class-level Events Handlers cXMLHttpRequest.onreadystatechange = null; cXMLHttpRequest.onopen = null; cXMLHttpRequest.onsend = null; cXMLHttpRequest.onabort = null; // Public Methods cXMLHttpRequest.prototype.open = function(sMethod, sUrl, bAsync, sUser, sPassword) { // Delete headers, required when object is reused delete this._headers; // When bAsync parameter value is omitted, use true as default if (arguments.length < 3) bAsync = true; // Save async parameter for fixing Gecko bug with missing readystatechange in synchronous requests this._async = bAsync; // Set the onreadystatechange handler var oRequest = this, nState = this.readyState, fOnUnload; // BUGFIX: IE - memory leak on page unload (inter-page leak) if (bIE && bAsync) { fOnUnload = function() { if (nState != cXMLHttpRequest.DONE) { fCleanTransport(oRequest); // Safe to abort here since onreadystatechange handler removed oRequest.abort(); } }; window.attachEvent("onunload", fOnUnload); } // Add method sniffer if (cXMLHttpRequest.onopen) cXMLHttpRequest.onopen.apply(this, arguments); if (arguments.length > 4) this._object.open(sMethod, sUrl, bAsync, sUser, sPassword); else if (arguments.length > 3) this._object.open(sMethod, sUrl, bAsync, sUser); else this._object.open(sMethod, sUrl, bAsync); this.readyState = cXMLHttpRequest.OPENED; fReadyStateChange(this); this._object.onreadystatechange = function() { if (bGecko && !bAsync) return; // Synchronize state oRequest.readyState = oRequest._object.readyState; // fSynchronizeValues(oRequest); // BUGFIX: Firefox fires unnecessary DONE when aborting if (oRequest._aborted) { // Reset readyState to UNSENT oRequest.readyState = cXMLHttpRequest.UNSENT; // Return now return; } if (oRequest.readyState == cXMLHttpRequest.DONE) { // Free up queue delete oRequest._data; /* if (bAsync) fQueue_remove(oRequest);*/ // fCleanTransport(oRequest); // Uncomment this block if you need a fix for IE cache /* // BUGFIX: IE - cache issue if (!oRequest._object.getResponseHeader("Date")) { // Save object to cache oRequest._cached = oRequest._object; // Instantiate a new transport object cXMLHttpRequest.call(oRequest); // Re-send request if (sUser) { if (sPassword) oRequest._object.open(sMethod, sUrl, bAsync, sUser, sPassword); else oRequest._object.open(sMethod, sUrl, bAsync, sUser); } else oRequest._object.open(sMethod, sUrl, bAsync); oRequest._object.setRequestHeader("If-Modified-Since", oRequest._cached.getResponseHeader("Last-Modified") || new window.Date(0)); // Copy headers set if (oRequest._headers) for (var sHeader in oRequest._headers) if (typeof oRequest._headers[sHeader] == "string") // Some frameworks prototype objects with functions oRequest._object.setRequestHeader(sHeader, oRequest._headers[sHeader]); oRequest._object.onreadystatechange = function() { // Synchronize state oRequest.readyState = oRequest._object.readyState; if (oRequest._aborted) { // oRequest.readyState = cXMLHttpRequest.UNSENT; // Return return; } if (oRequest.readyState == cXMLHttpRequest.DONE) { // Clean Object fCleanTransport(oRequest); // get cached request if (oRequest.status == 304) oRequest._object = oRequest._cached; // delete oRequest._cached; // fSynchronizeValues(oRequest); // fReadyStateChange(oRequest); // BUGFIX: IE - memory leak in interrupted if (bIE && bAsync) window.detachEvent("onunload", fOnUnload); } }; oRequest._object.send(null); // Return now - wait until re-sent request is finished return; }; */ // BUGFIX: IE - memory leak in interrupted if (bIE && bAsync) window.detachEvent("onunload", fOnUnload); } // BUGFIX: Some browsers (Internet Explorer, Gecko) fire OPEN readystate twice if (nState != oRequest.readyState) fReadyStateChange(oRequest); nState = oRequest.readyState; } }; function fXMLHttpRequest_send(oRequest) { oRequest._object.send(oRequest._data); // BUGFIX: Gecko - missing readystatechange calls in synchronous requests if (bGecko && !oRequest._async) { oRequest.readyState = cXMLHttpRequest.OPENED; // Synchronize state fSynchronizeValues(oRequest); // Simulate missing states while (oRequest.readyState < cXMLHttpRequest.DONE) { oRequest.readyState++; fReadyStateChange(oRequest); // Check if we are aborted if (oRequest._aborted) return; } } }; cXMLHttpRequest.prototype.send = function(vData) { // Add method sniffer if (cXMLHttpRequest.onsend) cXMLHttpRequest.onsend.apply(this, arguments); if (!arguments.length) vData = null; // BUGFIX: Safari - fails sending documents created/modified dynamically, so an explicit serialization required // BUGFIX: IE - rewrites any custom mime-type to "text/xml" in case an XMLNode is sent // BUGFIX: Gecko - fails sending Element (this is up to the implementation either to standard) if (vData && vData.nodeType) { vData = window.XMLSerializer ? new window.XMLSerializer().serializeToString(vData) : vData.xml; if (!this._headers["Content-Type"]) this._object.setRequestHeader("Content-Type", "application/xml"); } this._data = vData; /* // Add to queue if (this._async) fQueue_add(this); else*/ fXMLHttpRequest_send(this); }; cXMLHttpRequest.prototype.abort = function() { // Add method sniffer if (cXMLHttpRequest.onabort) cXMLHttpRequest.onabort.apply(this, arguments); // BUGFIX: Gecko - unnecessary DONE when aborting if (this.readyState > cXMLHttpRequest.UNSENT) this._aborted = true; this._object.abort(); // BUGFIX: IE - memory leak fCleanTransport(this); this.readyState = cXMLHttpRequest.UNSENT; delete this._data; /* if (this._async) fQueue_remove(this);*/ }; cXMLHttpRequest.prototype.getAllResponseHeaders = function() { return this._object.getAllResponseHeaders(); }; cXMLHttpRequest.prototype.getResponseHeader = function(sName) { return this._object.getResponseHeader(sName); }; cXMLHttpRequest.prototype.setRequestHeader = function(sName, sValue) { // BUGFIX: IE - cache issue if (!this._headers) this._headers = {}; this._headers[sName] = sValue; return this._object.setRequestHeader(sName, sValue); }; // EventTarget interface implementation cXMLHttpRequest.prototype.addEventListener = function(sName, fHandler, bUseCapture) { for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++) if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture) return; // Add listener this._listeners.push([sName, fHandler, bUseCapture]); }; cXMLHttpRequest.prototype.removeEventListener = function(sName, fHandler, bUseCapture) { for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++) if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture) break; // Remove listener if (oListener) this._listeners.splice(nIndex, 1); }; cXMLHttpRequest.prototype.dispatchEvent = function(oEvent) { var oEventPseudo = { 'type': oEvent.type, 'target': this, 'currentTarget':this, 'eventPhase': 2, 'bubbles': oEvent.bubbles, 'cancelable': oEvent.cancelable, 'timeStamp': oEvent.timeStamp, 'stopPropagation': function() {}, // There is no flow 'preventDefault': function() {}, // There is no default action 'initEvent': function() {} // Original event object should be initialized }; // Execute onreadystatechange if (oEventPseudo.type == "readystatechange" && this.onreadystatechange) (this.onreadystatechange.handleEvent || this.onreadystatechange).apply(this, [oEventPseudo]); // Execute listeners for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++) if (oListener[0] == oEventPseudo.type && !oListener[2]) (oListener[1].handleEvent || oListener[1]).apply(this, [oEventPseudo]); }; // cXMLHttpRequest.prototype.toString = function() { return '[' + "object" + ' ' + "XMLHttpRequest" + ']'; }; cXMLHttpRequest.toString = function() { return '[' + "XMLHttpRequest" + ']'; }; // Helper function function fReadyStateChange(oRequest) { // Sniffing code if (cXMLHttpRequest.onreadystatechange) cXMLHttpRequest.onreadystatechange.apply(oRequest); // Fake event oRequest.dispatchEvent({ 'type': "readystatechange", 'bubbles': false, 'cancelable': false, 'timeStamp': new Date + 0 }); }; function fGetDocument(oRequest) { var oDocument = oRequest.responseXML, sResponse = oRequest.responseText; // Try parsing responseText if (bIE && sResponse && oDocument && !oDocument.documentElement && oRequest.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/)) { oDocument = new window.ActiveXObject("Microsoft.XMLDOM"); oDocument.async = false; oDocument.validateOnParse = false; oDocument.loadXML(sResponse); } // Check if there is no error in document if (oDocument) if ((bIE && oDocument.parseError != 0) || !oDocument.documentElement || (oDocument.documentElement && oDocument.documentElement.tagName == "parsererror")) return null; return oDocument; }; function fSynchronizeValues(oRequest) { try { oRequest.responseText = oRequest._object.responseText; } catch (e) {} try { oRequest.responseXML = fGetDocument(oRequest._object); } catch (e) {} try { oRequest.status = oRequest._object.status; } catch (e) {} try { oRequest.statusText = oRequest._object.statusText; } catch (e) {} }; function fCleanTransport(oRequest) { // BUGFIX: IE - memory leak (on-page leak) oRequest._object.onreadystatechange = new window.Function; }; /* // Queue manager var oQueuePending = {"CRITICAL":[],"HIGH":[],"NORMAL":[],"LOW":[],"LOWEST":[]}, aQueueRunning = []; function fQueue_add(oRequest) { oQueuePending[oRequest.priority in oQueuePending ? oRequest.priority : "NORMAL"].push(oRequest); // setTimeout(fQueue_process); }; function fQueue_remove(oRequest) { for (var nIndex = 0, bFound = false; nIndex < aQueueRunning.length; nIndex++) if (bFound) aQueueRunning[nIndex - 1] = aQueueRunning[nIndex]; else if (aQueueRunning[nIndex] == oRequest) bFound = true; if (bFound) aQueueRunning.length--; // setTimeout(fQueue_process); }; function fQueue_process() { if (aQueueRunning.length < 6) { for (var sPriority in oQueuePending) { if (oQueuePending[sPriority].length) { var oRequest = oQueuePending[sPriority][0]; oQueuePending[sPriority] = oQueuePending[sPriority].slice(1); // aQueueRunning.push(oRequest); // Send request fXMLHttpRequest_send(oRequest); break; } } } }; */ // Internet Explorer 5.0 (missing apply) if (!window.Function.prototype.apply) { window.Function.prototype.apply = function(oRequest, oArguments) { if (!oArguments) oArguments = []; oRequest.__func = this; oRequest.__func(oArguments[0], oArguments[1], oArguments[2], oArguments[3], oArguments[4]); delete oRequest.__func; }; }; // Register new object with window /** * Class: OpenLayers.Request.XMLHttpRequest * Standard-compliant (W3C) cross-browser implementation of the * XMLHttpRequest object. From * http://code.google.com/p/xmlhttprequest/. */ if (!OpenLayers.Request) { /** * This allows for OpenLayers/Request.js to be included * before or after this script. */ OpenLayers.Request = {}; } OpenLayers.Request.XMLHttpRequest = cXMLHttpRequest; })(); /* ====================================================================== OpenLayers/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.Comparison = OpenLayers.Class(OpenLayers.Filter, { /** * APIProperty: type * {String} type: type of the comparison. This is one of * - OpenLayers.Filter.Comparison.EQUAL_TO = "=="; * - OpenLayers.Filter.Comparison.NOT_EQUAL_TO = "!="; * - OpenLayers.Filter.Comparison.LESS_THAN = "<"; * - OpenLayers.Filter.Comparison.GREATER_THAN = ">"; * - OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO = "<="; * - OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO = ">="; * - OpenLayers.Filter.Comparison.BETWEEN = ".."; * - OpenLayers.Filter.Comparison.LIKE = "~"; * - OpenLayers.Filter.Comparison.IS_NULL = "NULL"; */ type: null, /** * APIProperty: property * {String} * name of the context property to compare */ property: null, /** * APIProperty: value * {Number} or {String} * comparison value for binary comparisons. In the case of a String, this * can be a combination of text and propertyNames in the form * "literal ${propertyName}" */ value: null, /** * Property: matchCase * {Boolean} Force case sensitive searches for EQUAL_TO and NOT_EQUAL_TO * comparisons. The Filter Encoding 1.1 specification added a matchCase * attribute to ogc:PropertyIsEqualTo and ogc:PropertyIsNotEqualTo * elements. This property will be serialized with those elements only * if using the v1.1.0 filter format. However, when evaluating filters * here, the matchCase property will always be respected (for EQUAL_TO * and NOT_EQUAL_TO). Default is true. */ matchCase: true, /** * APIProperty: lowerBoundary * {Number} or {String} * lower boundary for between comparisons. In the case of a String, this * can be a combination of text and propertyNames in the form * "literal ${propertyName}" */ lowerBoundary: null, /** * APIProperty: upperBoundary * {Number} or {String} * upper boundary for between comparisons. In the case of a String, this * can be a combination of text and propertyNames in the form * "literal ${propertyName}" */ upperBoundary: null, /** * Constructor: OpenLayers.Filter.Comparison * Creates a comparison rule. * * Parameters: * options - {Object} An optional object with properties to set on the * rule * * Returns: * {} */ initialize: function(options) { OpenLayers.Filter.prototype.initialize.apply(this, [options]); // since matchCase on PropertyIsLike is not schema compliant, we only // want to use this if explicitly asked for if (this.type === OpenLayers.Filter.Comparison.LIKE && options.matchCase === undefined) { this.matchCase = null; } }, /** * APIMethod: evaluate * Evaluates this filter in a specific context. * * Parameters: * context - {Object} Context to use in evaluating the filter. If a vector * feature is provided, the feature.attributes will be used as context. * * Returns: * {Boolean} The filter applies. */ evaluate: function(context) { if (context instanceof OpenLayers.Feature.Vector) { context = context.attributes; } var result = false; var got = context[this.property]; var exp; switch(this.type) { case OpenLayers.Filter.Comparison.EQUAL_TO: exp = this.value; if(!this.matchCase && typeof got == "string" && typeof exp == "string") { result = (got.toUpperCase() == exp.toUpperCase()); } else { result = (got == exp); } break; case OpenLayers.Filter.Comparison.NOT_EQUAL_TO: exp = this.value; if(!this.matchCase && typeof got == "string" && typeof exp == "string") { result = (got.toUpperCase() != exp.toUpperCase()); } else { result = (got != exp); } break; case OpenLayers.Filter.Comparison.LESS_THAN: result = got < this.value; break; case OpenLayers.Filter.Comparison.GREATER_THAN: result = got > this.value; break; case OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO: result = got <= this.value; break; case OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO: result = got >= this.value; break; case OpenLayers.Filter.Comparison.BETWEEN: result = (got >= this.lowerBoundary) && (got <= this.upperBoundary); break; case OpenLayers.Filter.Comparison.LIKE: var regexp = new RegExp(this.value, "gi"); result = regexp.test(got); break; case OpenLayers.Filter.Comparison.IS_NULL: result = (got === null); break; } return result; }, /** * APIMethod: value2regex * Converts the value of this rule into a regular expression string, * according to the wildcard characters specified. This method has to * be called after instantiation of this class, if the value is not a * regular expression already. * * Parameters: * wildCard - {Char} wildcard character in the above value, default * is "*" * singleChar - {Char} single-character wildcard in the above value * default is "." * escapeChar - {Char} escape character in the above value, default is * "!" * * Returns: * {String} regular expression string */ value2regex: function(wildCard, singleChar, escapeChar) { if (wildCard == ".") { throw new Error("'.' is an unsupported wildCard character for " + "OpenLayers.Filter.Comparison"); } // set UMN MapServer defaults for unspecified parameters wildCard = wildCard ? wildCard : "*"; singleChar = singleChar ? singleChar : "."; escapeChar = escapeChar ? escapeChar : "!"; this.value = this.value.replace( new RegExp("\\"+escapeChar+"(.|$)", "g"), "\\$1"); this.value = this.value.replace( new RegExp("\\"+singleChar, "g"), "."); this.value = this.value.replace( new RegExp("\\"+wildCard, "g"), ".*"); this.value = this.value.replace( new RegExp("\\\\.\\*", "g"), "\\"+wildCard); this.value = this.value.replace( new RegExp("\\\\\\.", "g"), "\\"+singleChar); return this.value; }, /** * Method: regex2value * Convert the value of this rule from a regular expression string into an * ogc literal string using a wildCard of *, a singleChar of ., and an * escape of !. Leaves the property unmodified. * * Returns: * {String} A string value. */ regex2value: function() { var value = this.value; // replace ! with !! value = value.replace(/!/g, "!!"); // replace \. with !. (watching out for \\.) value = value.replace(/(\\)?\\\./g, function($0, $1) { return $1 ? $0 : "!."; }); // replace \* with #* (watching out for \\*) value = value.replace(/(\\)?\\\*/g, function($0, $1) { return $1 ? $0 : "!*"; }); // replace \\ with \ value = value.replace(/\\\\/g, "\\"); // convert .* to * (the sequence #.* is not allowed) value = value.replace(/\.\*/g, "*"); return value; }, /** * APIMethod: clone * Clones this filter. * * Returns: * {} Clone of this filter. */ clone: function() { return OpenLayers.Util.extend(new OpenLayers.Filter.Comparison(), this); }, CLASS_NAME: "OpenLayers.Filter.Comparison" }); OpenLayers.Filter.Comparison.EQUAL_TO = "=="; OpenLayers.Filter.Comparison.NOT_EQUAL_TO = "!="; OpenLayers.Filter.Comparison.LESS_THAN = "<"; OpenLayers.Filter.Comparison.GREATER_THAN = ">"; OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO = "<="; OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO = ">="; OpenLayers.Filter.Comparison.BETWEEN = ".."; OpenLayers.Filter.Comparison.LIKE = "~"; OpenLayers.Filter.Comparison.IS_NULL = "NULL"; /* ====================================================================== OpenLayers/Popup/FramedCloud.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of 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/Popup/Framed.js * @requires OpenLayers/Util.js * @requires OpenLayers/BaseTypes/Bounds.js * @requires OpenLayers/BaseTypes/Pixel.js * @requires OpenLayers/BaseTypes/Size.js */ /** * Class: OpenLayers.Popup.FramedCloud * * Inherits from: * - */ OpenLayers.Popup.FramedCloud = OpenLayers.Class(OpenLayers.Popup.Framed, { /** * Property: contentDisplayClass * {String} The CSS class of the popup content div. */ contentDisplayClass: "olFramedCloudPopupContent", /** * APIProperty: autoSize * {Boolean} Framed Cloud is autosizing by default. */ autoSize: true, /** * APIProperty: panMapIfOutOfView * {Boolean} Framed Cloud does pan into view by default. */ panMapIfOutOfView: true, /** * APIProperty: imageSize * {} */ imageSize: new OpenLayers.Size(1276, 736), /** * APIProperty: isAlphaImage * {Boolean} The FramedCloud does not use an alpha image (in honor of the * good ie6 folk out there) */ isAlphaImage: false, /** * APIProperty: fixedRelativePosition * {Boolean} The Framed Cloud popup works in just one fixed position. */ fixedRelativePosition: false, /** * Property: positionBlocks * {Object} Hash of differen position blocks, keyed by relativePosition * two-character code string (ie "tl", "tr", "bl", "br") */ positionBlocks: { "tl": { 'offset': new OpenLayers.Pixel(44, 0), 'padding': new OpenLayers.Bounds(8, 40, 8, 9), 'blocks': [ { // top-left size: new OpenLayers.Size('auto', 'auto'), anchor: new OpenLayers.Bounds(0, 51, 22, 0), position: new OpenLayers.Pixel(0, 0) }, { //top-right size: new OpenLayers.Size(22, 'auto'), anchor: new OpenLayers.Bounds(null, 50, 0, 0), position: new OpenLayers.Pixel(-1238, 0) }, { //bottom-left size: new OpenLayers.Size('auto', 19), anchor: new OpenLayers.Bounds(0, 32, 22, null), position: new OpenLayers.Pixel(0, -631) }, { //bottom-right size: new OpenLayers.Size(22, 18), anchor: new OpenLayers.Bounds(null, 32, 0, null), position: new OpenLayers.Pixel(-1238, -632) }, { // stem size: new OpenLayers.Size(81, 35), anchor: new OpenLayers.Bounds(null, 0, 0, null), position: new OpenLayers.Pixel(0, -688) } ] }, "tr": { 'offset': new OpenLayers.Pixel(-45, 0), 'padding': new OpenLayers.Bounds(8, 40, 8, 9), 'blocks': [ { // top-left size: new OpenLayers.Size('auto', 'auto'), anchor: new OpenLayers.Bounds(0, 51, 22, 0), position: new OpenLayers.Pixel(0, 0) }, { //top-right size: new OpenLayers.Size(22, 'auto'), anchor: new OpenLayers.Bounds(null, 50, 0, 0), position: new OpenLayers.Pixel(-1238, 0) }, { //bottom-left size: new OpenLayers.Size('auto', 19), anchor: new OpenLayers.Bounds(0, 32, 22, null), position: new OpenLayers.Pixel(0, -631) }, { //bottom-right size: new OpenLayers.Size(22, 19), anchor: new OpenLayers.Bounds(null, 32, 0, null), position: new OpenLayers.Pixel(-1238, -631) }, { // stem size: new OpenLayers.Size(81, 35), anchor: new OpenLayers.Bounds(0, 0, null, null), position: new OpenLayers.Pixel(-215, -687) } ] }, "bl": { 'offset': new OpenLayers.Pixel(45, 0), 'padding': new OpenLayers.Bounds(8, 9, 8, 40), 'blocks': [ { // top-left size: new OpenLayers.Size('auto', 'auto'), anchor: new OpenLayers.Bounds(0, 21, 22, 32), position: new OpenLayers.Pixel(0, 0) }, { //top-right size: new OpenLayers.Size(22, 'auto'), anchor: new OpenLayers.Bounds(null, 21, 0, 32), position: new OpenLayers.Pixel(-1238, 0) }, { //bottom-left size: new OpenLayers.Size('auto', 21), anchor: new OpenLayers.Bounds(0, 0, 22, null), position: new OpenLayers.Pixel(0, -629) }, { //bottom-right size: new OpenLayers.Size(22, 21), anchor: new OpenLayers.Bounds(null, 0, 0, null), position: new OpenLayers.Pixel(-1238, -629) }, { // stem size: new OpenLayers.Size(81, 33), anchor: new OpenLayers.Bounds(null, null, 0, 0), position: new OpenLayers.Pixel(-101, -674) } ] }, "br": { 'offset': new OpenLayers.Pixel(-44, 0), 'padding': new OpenLayers.Bounds(8, 9, 8, 40), 'blocks': [ { // top-left size: new OpenLayers.Size('auto', 'auto'), anchor: new OpenLayers.Bounds(0, 21, 22, 32), position: new OpenLayers.Pixel(0, 0) }, { //top-right size: new OpenLayers.Size(22, 'auto'), anchor: new OpenLayers.Bounds(null, 21, 0, 32), position: new OpenLayers.Pixel(-1238, 0) }, { //bottom-left size: new OpenLayers.Size('auto', 21), anchor: new OpenLayers.Bounds(0, 0, 22, null), position: new OpenLayers.Pixel(0, -629) }, { //bottom-right size: new OpenLayers.Size(22, 21), anchor: new OpenLayers.Bounds(null, 0, 0, null), position: new OpenLayers.Pixel(-1238, -629) }, { // stem size: new OpenLayers.Size(81, 33), anchor: new OpenLayers.Bounds(0, null, null, 0), position: new OpenLayers.Pixel(-311, -674) } ] } }, /** * APIProperty: minSize * {} */ minSize: new OpenLayers.Size(105, 10), /** * APIProperty: maxSize * {} */ maxSize: new OpenLayers.Size(1200, 660), /** * Constructor: OpenLayers.Popup.FramedCloud * * Parameters: * id - {String} * lonlat - {} * contentSize - {} * contentHTML - {String} * anchor - {Object} Object to which we'll anchor the popup. Must expose * a 'size' () and 'offset' () * (Note that this is generally an ). * closeBox - {Boolean} * closeBoxCallback - {Function} Function to be called on closeBox click. */ initialize:function(id, lonlat, contentSize, contentHTML, anchor, closeBox, closeBoxCallback) { this.imageSrc = OpenLayers.Util.getImageLocation('cloud-popup-relative.png'); OpenLayers.Popup.Framed.prototype.initialize.apply(this, arguments); this.contentDiv.className = this.contentDisplayClass; }, CLASS_NAME: "OpenLayers.Popup.FramedCloud" }); /* ====================================================================== OpenLayers/Rule.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/BaseTypes/Class.js * @requires OpenLayers/Util.js * @requires OpenLayers/Style.js */ /** * Class: OpenLayers.Rule * This class represents an SLD Rule, as being used for rule-based SLD styling. */ OpenLayers.Rule = OpenLayers.Class({ /** * Property: id * {String} A unique id for this session. */ id: null, /** * APIProperty: name * {String} name of this rule */ name: null, /** * Property: title * {String} Title of this rule (set if included in SLD) */ title: null, /** * Property: description * {String} Description of this rule (set if abstract is included in SLD) */ description: null, /** * Property: context * {Object} An optional object with properties that the rule should be * evaluated against. If no context is specified, feature.attributes will * be used. */ context: null, /** * Property: filter * {} Optional filter for the rule. */ filter: null, /** * Property: elseFilter * {Boolean} Determines whether this rule is only to be applied only if * no other rules match (ElseFilter according to the SLD specification). * Default is false. For instances of OpenLayers.Rule, if elseFilter is * false, the rule will always apply. For subclasses, the else property is * ignored. */ elseFilter: false, /** * Property: symbolizer * {Object} Symbolizer or hash of symbolizers for this rule. If hash of * symbolizers, keys are one or more of ["Point", "Line", "Polygon"]. The * latter if useful if it is required to style e.g. vertices of a line * with a point symbolizer. Note, however, that this is not implemented * yet in OpenLayers, but it is the way how symbolizers are defined in * SLD. */ symbolizer: null, /** * Property: symbolizers * {Array} Collection of symbolizers associated with this rule. If * provided at construction, the symbolizers array has precedence * over the deprecated symbolizer property. Note that multiple * symbolizers are not currently supported by the vector renderers. * Rules with multiple symbolizers are currently only useful for * maintaining elements in an SLD document. */ symbolizers: null, /** * APIProperty: minScaleDenominator * {Number} or {String} minimum scale at which to draw the feature. * In the case of a String, this can be a combination of text and * propertyNames in the form "literal ${propertyName}" */ minScaleDenominator: null, /** * APIProperty: maxScaleDenominator * {Number} or {String} maximum scale at which to draw the feature. * In the case of a String, this can be a combination of text and * propertyNames in the form "literal ${propertyName}" */ maxScaleDenominator: null, /** * Constructor: OpenLayers.Rule * Creates a Rule. * * Parameters: * options - {Object} An optional object with properties to set on the * rule * * Returns: * {} */ initialize: function(options) { this.symbolizer = {}; OpenLayers.Util.extend(this, options); if (this.symbolizers) { delete this.symbolizer; } this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_"); }, /** * APIMethod: destroy * nullify references to prevent circular references and memory leaks */ destroy: function() { for (var i in this.symbolizer) { this.symbolizer[i] = null; } this.symbolizer = null; delete this.symbolizers; }, /** * APIMethod: evaluate * evaluates this rule for a specific feature * * Parameters: * feature - {} feature to apply the rule to. * * Returns: * {Boolean} true if the rule applies, false if it does not. * This rule is the default rule and always returns true. */ evaluate: function(feature) { var context = this.getContext(feature); var applies = true; if (this.minScaleDenominator || this.maxScaleDenominator) { var scale = feature.layer.map.getScale(); } // check if within minScale/maxScale bounds if (this.minScaleDenominator) { applies = scale >= OpenLayers.Style.createLiteral( this.minScaleDenominator, context); } if (applies && this.maxScaleDenominator) { applies = scale < OpenLayers.Style.createLiteral( this.maxScaleDenominator, context); } // check if optional filter applies if(applies && this.filter) { // feature id filters get the feature, others get the context if(this.filter.CLASS_NAME == "OpenLayers.Filter.FeatureId") { applies = this.filter.evaluate(feature); } else { applies = this.filter.evaluate(context); } } return applies; }, /** * Method: getContext * Gets the context for evaluating this rule * * Paramters: * feature - {} feature to take the context from if * none is specified. */ getContext: function(feature) { var context = this.context; if (!context) { context = feature.attributes || feature.data; } if (typeof this.context == "function") { context = this.context(feature); } return context; }, /** * APIMethod: clone * Clones this rule. * * Returns: * {} Clone of this rule. */ clone: function() { var options = OpenLayers.Util.extend({}, this); if (this.symbolizers) { // clone symbolizers var len = this.symbolizers.length; options.symbolizers = new Array(len); for (var i=0; i constructor. * * Note that for all calculations in this class, we use (num | 0) to truncate a * float value to an integer. This is done because it seems that VML doesn't * support float values. * * Inherits from: * - */ OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, { /** * Property: xmlns * {String} XML Namespace URN */ xmlns: "urn:schemas-microsoft-com:vml", /** * Property: symbolCache * {DOMElement} node holding symbols. This hash is keyed by symbol name, * and each value is a hash with a "path" and an "extent" property. */ symbolCache: {}, /** * Property: offset * {Object} Hash with "x" and "y" properties */ offset: null, /** * Constructor: OpenLayers.Renderer.VML * Create a new VML renderer. * * Parameters: * containerID - {String} The id for the element that contains the renderer */ initialize: function(containerID) { if (!this.supported()) { return; } if (!document.namespaces.olv) { document.namespaces.add("olv", this.xmlns); var style = document.createStyleSheet(); var shapes = ['shape','rect', 'oval', 'fill', 'stroke', 'imagedata', 'group','textbox']; for (var i = 0, len = shapes.length; i < len; i++) { style.addRule('olv\\:' + shapes[i], "behavior: url(#default#VML); " + "position: absolute; display: inline-block;"); } } OpenLayers.Renderer.Elements.prototype.initialize.apply(this, arguments); }, /** * APIMethod: supported * Determine whether a browser supports this renderer. * * Returns: * {Boolean} The browser supports the VML renderer */ supported: function() { return !!(document.namespaces); }, /** * Method: setExtent * Set the renderer's extent * * Parameters: * extent - {} * resolutionChanged - {Boolean} * * Returns: * {Boolean} true to notify the layer that the new extent does not exceed * the coordinate range, and the features will not need to be redrawn. */ setExtent: function(extent, resolutionChanged) { var coordSysUnchanged = OpenLayers.Renderer.Elements.prototype.setExtent.apply(this, arguments); var resolution = this.getResolution(); var left = (extent.left/resolution) | 0; var top = (extent.top/resolution - this.size.h) | 0; if (resolutionChanged || !this.offset) { this.offset = {x: left, y: top}; left = 0; top = 0; } else { left = left - this.offset.x; top = top - this.offset.y; } var org = (left - this.xOffset) + " " + top; this.root.coordorigin = org; var roots = [this.root, this.vectorRoot, this.textRoot]; var root; for(var i=0, len=roots.length; i} the size of the drawing surface */ setSize: function(size) { OpenLayers.Renderer.prototype.setSize.apply(this, arguments); // setting width and height on all roots to avoid flicker which we // would get with 100% width and height on child roots var roots = [ this.rendererRoot, this.root, this.vectorRoot, this.textRoot ]; var w = this.size.w + "px"; var h = this.size.h + "px"; var root; for(var i=0, len=roots.length; i} * 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 = "olv:rect"; } else if (this.isComplexSymbol(style.graphicName)) { nodeType = "olv:shape"; } else { nodeType = "olv:oval"; } break; case "OpenLayers.Geometry.Rectangle": nodeType = "olv:rect"; break; case "OpenLayers.Geometry.LineString": case "OpenLayers.Geometry.LinearRing": case "OpenLayers.Geometry.Polygon": case "OpenLayers.Geometry.Curve": nodeType = "olv:shape"; break; default: break; } return nodeType; }, /** * Method: setStyle * Use to set all the style attributes to a VML node. * * Parameters: * node - {DOMElement} An VML element to decorate * style - {Object} * options - {Object} Currently supported options include * 'isFilled' {Boolean} and * 'isStroked' {Boolean} * geometry - {} */ setStyle: function(node, style, options, geometry) { style = style || node._style; options = options || node._options; var fillColor = style.fillColor; var title = style.title || style.graphicTitle; if (title) { node.title = title; } if (node._geometryClass === "OpenLayers.Geometry.Point") { if (style.externalGraphic) { options.isFilled = true; 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 resolution = this.getResolution(); var xOffset = (style.graphicXOffset != undefined) ? style.graphicXOffset : -(0.5 * width); var yOffset = (style.graphicYOffset != undefined) ? style.graphicYOffset : -(0.5 * height); node.style.left = ((((geometry.x - this.featureDx)/resolution - this.offset.x)+xOffset) | 0) + "px"; node.style.top = (((geometry.y/resolution - this.offset.y)-(yOffset+height)) | 0) + "px"; node.style.width = width + "px"; node.style.height = height + "px"; node.style.flip = "y"; // modify fillColor and options for stroke styling below fillColor = "none"; options.isStroked = false; } else if (this.isComplexSymbol(style.graphicName)) { var cache = this.importSymbol(style.graphicName); node.path = cache.path; node.coordorigin = cache.left + "," + cache.bottom; var size = cache.size; node.coordsize = size + "," + size; this.drawCircle(node, geometry, style.pointRadius); node.style.flip = "y"; } else { this.drawCircle(node, geometry, style.pointRadius); } } // fill if (options.isFilled) { node.fillcolor = fillColor; } else { node.filled = "false"; } var fills = node.getElementsByTagName("fill"); var fill = (fills.length == 0) ? null : fills[0]; if (!options.isFilled) { if (fill) { node.removeChild(fill); } } else { if (!fill) { fill = this.createNode('olv:fill', node.id + "_fill"); } fill.opacity = style.fillOpacity; if (node._geometryClass === "OpenLayers.Geometry.Point" && style.externalGraphic) { // override fillOpacity if (style.graphicOpacity) { fill.opacity = style.graphicOpacity; } fill.src = style.externalGraphic; fill.type = "frame"; if (!(style.graphicWidth && style.graphicHeight)) { fill.aspect = "atmost"; } } if (fill.parentNode != node) { node.appendChild(fill); } } // additional rendering for rotated graphics or symbols var rotation = style.rotation; if ((rotation !== undefined || node._rotation !== undefined)) { node._rotation = rotation; if (style.externalGraphic) { this.graphicRotate(node, xOffset, yOffset, style); // make the fill fully transparent, because we now have // the graphic as imagedata element. We cannot just remove // the fill, because this is part of the hack described // in graphicRotate fill.opacity = 0; } else if(node._geometryClass === "OpenLayers.Geometry.Point") { node.style.rotation = rotation || 0; } } // stroke var strokes = node.getElementsByTagName("stroke"); var stroke = (strokes.length == 0) ? null : strokes[0]; if (!options.isStroked) { node.stroked = false; if (stroke) { stroke.on = false; } } else { if (!stroke) { stroke = this.createNode('olv:stroke', node.id + "_stroke"); node.appendChild(stroke); } stroke.on = true; stroke.color = style.strokeColor; stroke.weight = style.strokeWidth + "px"; stroke.opacity = style.strokeOpacity; stroke.endcap = style.strokeLinecap == 'butt' ? 'flat' : (style.strokeLinecap || 'round'); if (style.strokeDashstyle) { stroke.dashstyle = this.dashStyle(style); } } if (style.cursor != "inherit" && style.cursor != null) { node.style.cursor = style.cursor; } return node; }, /** * Method: graphicRotate * If a point is to be styled with externalGraphic and rotation, VML fills * cannot be used to display the graphic, because rotation of graphic * fills is not supported by the VML implementation of Internet Explorer. * This method creates a olv:imagedata element inside the VML node, * DXImageTransform.Matrix and BasicImage filters for rotation and * opacity, and a 3-step hack to remove rendering artefacts from the * graphic and preserve the ability of graphics to trigger events. * Finally, OpenLayers methods are used to determine the correct * insertion point of the rotated image, because DXImageTransform.Matrix * does the rotation without the ability to specify a rotation center * point. * * Parameters: * node - {DOMElement} * xOffset - {Number} rotation center relative to image, x coordinate * yOffset - {Number} rotation center relative to image, y coordinate * style - {Object} */ graphicRotate: function(node, xOffset, yOffset, style) { var style = style || node._style; var rotation = style.rotation || 0; var aspectRatio, size; if (!(style.graphicWidth && style.graphicHeight)) { // load the image to determine its size var img = new Image(); img.onreadystatechange = OpenLayers.Function.bind(function() { if(img.readyState == "complete" || img.readyState == "interactive") { aspectRatio = img.width / img.height; size = Math.max(style.pointRadius * 2, style.graphicWidth || 0, style.graphicHeight || 0); xOffset = xOffset * aspectRatio; style.graphicWidth = size * aspectRatio; style.graphicHeight = size; this.graphicRotate(node, xOffset, yOffset, style); } }, this); img.src = style.externalGraphic; // will be called again by the onreadystate handler return; } else { size = Math.max(style.graphicWidth, style.graphicHeight); aspectRatio = style.graphicWidth / style.graphicHeight; } var width = Math.round(style.graphicWidth || size * aspectRatio); var height = Math.round(style.graphicHeight || size); node.style.width = width + "px"; node.style.height = height + "px"; // Three steps are required to remove artefacts for images with // transparent backgrounds (resulting from using DXImageTransform // filters on svg objects), while preserving awareness for browser // events on images: // - Use the fill as usual (like for unrotated images) to handle // events // - specify an imagedata element with the same src as the fill // - style the imagedata element with an AlphaImageLoader filter // with empty src var image = document.getElementById(node.id + "_image"); if (!image) { image = this.createNode("olv:imagedata", node.id + "_image"); node.appendChild(image); } image.style.width = width + "px"; image.style.height = height + "px"; image.src = style.externalGraphic; image.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(" + "src='', sizingMethod='scale')"; var rot = rotation * Math.PI / 180; var sintheta = Math.sin(rot); var costheta = Math.cos(rot); // do the rotation on the image var filter = "progid:DXImageTransform.Microsoft.Matrix(M11=" + costheta + ",M12=" + (-sintheta) + ",M21=" + sintheta + ",M22=" + costheta + ",SizingMethod='auto expand')\n"; // set the opacity (needed for the imagedata) var opacity = style.graphicOpacity || style.fillOpacity; if (opacity && opacity != 1) { filter += "progid:DXImageTransform.Microsoft.BasicImage(opacity=" + opacity+")\n"; } node.style.filter = filter; // do the rotation again on a box, so we know the insertion point var centerPoint = new OpenLayers.Geometry.Point(-xOffset, -yOffset); var imgBox = new OpenLayers.Bounds(0, 0, width, height).toGeometry(); imgBox.rotate(style.rotation, centerPoint); var imgBounds = imgBox.getBounds(); node.style.left = Math.round( parseInt(node.style.left) + imgBounds.left) + "px"; node.style.top = Math.round( parseInt(node.style.top) - imgBounds.bottom) + "px"; }, /** * Method: postDraw * Does some node postprocessing to work around browser issues: * - Some versions of Internet Explorer seem to be unable to set fillcolor * and strokecolor to "none" correctly before the fill node is appended * to a visible vml node. This method takes care of that and sets * fillcolor and strokecolor again if needed. * - In some cases, a node won't become visible after being drawn. Setting * style.visibility to "visible" works around that. * * Parameters: * node - {DOMElement} */ postDraw: function(node) { node.style.visibility = "visible"; var fillColor = node._style.fillColor; var strokeColor = node._style.strokeColor; if (fillColor == "none" && node.fillcolor != fillColor) { node.fillcolor = fillColor; } if (strokeColor == "none" && node.strokecolor != strokeColor) { node.strokecolor = strokeColor; } }, /** * Method: setNodeDimension * Get the geometry's bounds, convert it to our vml coordinate system, * then set the node's position, size, and local coordinate system. * * Parameters: * node - {DOMElement} * geometry - {} */ setNodeDimension: function(node, geometry) { var bbox = geometry.getBounds(); if(bbox) { var resolution = this.getResolution(); var scaledBox = new OpenLayers.Bounds(((bbox.left - this.featureDx)/resolution - this.offset.x) | 0, (bbox.bottom/resolution - this.offset.y) | 0, ((bbox.right - this.featureDx)/resolution - this.offset.x) | 0, (bbox.top/resolution - this.offset.y) | 0); // Set the internal coordinate system to draw the path node.style.left = scaledBox.left + "px"; node.style.top = scaledBox.top + "px"; node.style.width = scaledBox.getWidth() + "px"; node.style.height = scaledBox.getHeight() + "px"; node.coordorigin = scaledBox.left + " " + scaledBox.top; node.coordsize = scaledBox.getWidth()+ " " + scaledBox.getHeight(); } }, /** * Method: dashStyle * * Parameters: * style - {Object} * * Returns: * {String} A VML compliant 'stroke-dasharray' value */ dashStyle: function(style) { var dash = style.strokeDashstyle; switch (dash) { case 'solid': case 'dot': case 'dash': case 'dashdot': case 'longdash': case 'longdashdot': return dash; default: // very basic guessing of dash style patterns var parts = dash.split(/[ ,]/); if (parts.length == 2) { if (1*parts[0] >= 2*parts[1]) { return "longdash"; } return (parts[0] == 1 || parts[1] == 1) ? "dot" : "dash"; } else if (parts.length == 4) { return (1*parts[0] >= 2*parts[1]) ? "longdashdot" : "dashdot"; } return "solid"; } }, /** * Method: createNode * Create a new node * * 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.createElement(type); if (id) { node.id = id; } // IE hack to make elements unselectable, to prevent 'blue flash' // while dragging vectors; #1410 node.unselectable = 'on'; node.onselectstart = OpenLayers.Function.False; return node; }, /** * Method: nodeTypeCompare * Determine whether a node is of a given type * * Parameters: * node - {DOMElement} An VML element * type - {String} Kind of node * * Returns: * {Boolean} Whether or not the specified node is of the specified type */ nodeTypeCompare: function(node, type) { //split type var subType = type; var splitIndex = subType.indexOf(":"); if (splitIndex != -1) { subType = subType.substr(splitIndex+1); } //split nodeName var nodeName = node.nodeName; splitIndex = nodeName.indexOf(":"); if (splitIndex != -1) { nodeName = nodeName.substr(splitIndex+1); } return (subType == nodeName); }, /** * Method: createRenderRoot * Create the renderer root * * Returns: * {DOMElement} The specific render engine's root element */ createRenderRoot: function() { return this.nodeFactory(this.container.id + "_vmlRoot", "div"); }, /** * Method: createRoot * Create the main root element * * Parameters: * suffix - {String} suffix to append to the id * * Returns: * {DOMElement} */ createRoot: function(suffix) { return this.nodeFactory(this.container.id + suffix, "olv:group"); }, /************************************** * * * GEOMETRY DRAWING FUNCTIONS * * * **************************************/ /** * Method: drawPoint * Render a point * * Parameters: * node - {DOMElement} * geometry - {} * * Returns: * {DOMElement} or false if the point could not be drawn */ drawPoint: function(node, geometry) { return this.drawCircle(node, geometry, 1); }, /** * Method: drawCircle * Render a circle. * Size and Center a circle given geometry (x,y center) and radius * * Parameters: * node - {DOMElement} * geometry - {} * radius - {float} * * Returns: * {DOMElement} or false if the circle could not ne drawn */ drawCircle: function(node, geometry, radius) { if(!isNaN(geometry.x)&& !isNaN(geometry.y)) { var resolution = this.getResolution(); node.style.left = ((((geometry.x - this.featureDx) /resolution - this.offset.x) | 0) - radius) + "px"; node.style.top = (((geometry.y /resolution - this.offset.y) | 0) - radius) + "px"; var diameter = radius * 2; node.style.width = diameter + "px"; node.style.height = diameter + "px"; return node; } return false; }, /** * Method: drawLineString * Render a linestring. * * Parameters: * node - {DOMElement} * geometry - {} * * Returns: * {DOMElement} */ drawLineString: function(node, geometry) { return this.drawLine(node, geometry, false); }, /** * Method: drawLinearRing * Render a linearring * * Parameters: * node - {DOMElement} * geometry - {} * * Returns: * {DOMElement} */ drawLinearRing: function(node, geometry) { return this.drawLine(node, geometry, true); }, /** * Method: DrawLine * Render a line. * * Parameters: * node - {DOMElement} * geometry - {} * closeLine - {Boolean} Close the line? (make it a ring?) * * Returns: * {DOMElement} */ drawLine: function(node, geometry, closeLine) { this.setNodeDimension(node, geometry); var resolution = this.getResolution(); var numComponents = geometry.components.length; var parts = new Array(numComponents); var comp, x, y; for (var i = 0; i < numComponents; i++) { comp = geometry.components[i]; x = ((comp.x - this.featureDx)/resolution - this.offset.x) | 0; y = (comp.y/resolution - this.offset.y) | 0; parts[i] = " " + x + "," + y + " l "; } var end = (closeLine) ? " x e" : " e"; node.path = "m" + parts.join("") + end; return node; }, /** * Method: drawPolygon * Render a polygon * * Parameters: * node - {DOMElement} * geometry - {} * * Returns: * {DOMElement} */ drawPolygon: function(node, geometry) { this.setNodeDimension(node, geometry); var resolution = this.getResolution(); var path = []; var j, jj, points, area, first, second, i, ii, comp, pathComp, x, y; for (j=0, jj=geometry.components.length; j} * * Returns: * {DOMElement} */ drawRectangle: function(node, geometry) { var resolution = this.getResolution(); node.style.left = (((geometry.x - this.featureDx)/resolution - this.offset.x) | 0) + "px"; node.style.top = ((geometry.y/resolution - this.offset.y) | 0) + "px"; node.style.width = ((geometry.width/resolution) | 0) + "px"; node.style.height = ((geometry.height/resolution) | 0) + "px"; return node; }, /** * Method: drawText * This method is only called by the renderer itself. * * Parameters: * featureId - {String} * style - * location - {} */ drawText: function(featureId, style, location) { var label = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX, "olv:rect"); var textbox = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX + "_textbox", "olv:textbox"); var resolution = this.getResolution(); label.style.left = (((location.x - this.featureDx)/resolution - this.offset.x) | 0) + "px"; label.style.top = ((location.y/resolution - this.offset.y) | 0) + "px"; label.style.flip = "y"; textbox.innerText = style.label; if (style.cursor != "inherit" && style.cursor != null) { textbox.style.cursor = style.cursor; } if (style.fontColor) { textbox.style.color = style.fontColor; } if (style.fontOpacity) { textbox.style.filter = 'alpha(opacity=' + (style.fontOpacity * 100) + ')'; } if (style.fontFamily) { textbox.style.fontFamily = style.fontFamily; } if (style.fontSize) { textbox.style.fontSize = style.fontSize; } if (style.fontWeight) { textbox.style.fontWeight = style.fontWeight; } if (style.fontStyle) { textbox.style.fontStyle = style.fontStyle; } if(style.labelSelect === true) { label._featureId = featureId; textbox._featureId = featureId; textbox._geometry = location; textbox._geometryClass = location.CLASS_NAME; } textbox.style.whiteSpace = "nowrap"; // fun with IE: IE7 in standards compliant mode does not display any // text with a left inset of 0. So we set this to 1px and subtract one // pixel later when we set label.style.left textbox.inset = "1px,0px,0px,0px"; if(!label.parentNode) { label.appendChild(textbox); this.textRoot.appendChild(label); } var align = style.labelAlign || "cm"; if (align.length == 1) { align += "m"; } var xshift = textbox.clientWidth * (OpenLayers.Renderer.VML.LABEL_SHIFT[align.substr(0,1)]); var yshift = textbox.clientHeight * (OpenLayers.Renderer.VML.LABEL_SHIFT[align.substr(1,1)]); label.style.left = parseInt(label.style.left)-xshift-1+"px"; label.style.top = parseInt(label.style.top)+yshift+"px"; }, /** * Method: moveRoot * moves this renderer's root to a different renderer. * * Parameters: * renderer - {} target renderer for the moved root * root - {DOMElement} optional root node. To be used when this renderer * holds roots from multiple layers to tell this method which one to * detach * * Returns: * {Boolean} true if successful, false otherwise */ moveRoot: function(renderer) { var layer = this.map.getLayer(renderer.container.id); if(layer instanceof OpenLayers.Layer.Vector.RootContainer) { layer = this.map.getLayer(this.container.id); } layer && layer.renderer.clear(); OpenLayers.Renderer.Elements.prototype.moveRoot.apply(this, arguments); layer && layer.redraw(); }, /** * Method: importSymbol * add a new symbol definition from the rendererer's symbol hash * * Parameters: * graphicName - {String} name of the symbol to import * * Returns: * {Object} - hash of {DOMElement} "symbol" and {Number} "size" */ importSymbol: function (graphicName) { var id = this.container.id + "-" + graphicName; // check if symbol already exists in the cache var cache = this.symbolCache[id]; if (cache) { return cache; } var symbol = OpenLayers.Renderer.symbol[graphicName]; if (!symbol) { throw new Error(graphicName + ' is not a valid symbol name'); } var symbolExtent = new OpenLayers.Bounds( Number.MAX_VALUE, Number.MAX_VALUE, 0, 0); var pathitems = ["m"]; for (var i=0; i 0) { symbolExtent.bottom = symbolExtent.bottom - diff; symbolExtent.top = symbolExtent.top + diff; } else { symbolExtent.left = symbolExtent.left + diff; symbolExtent.right = symbolExtent.right - diff; } cache = { path: path, size: symbolExtent.getWidth(), // equals getHeight() now left: symbolExtent.left, bottom: symbolExtent.bottom }; this.symbolCache[id] = cache; return cache; }, CLASS_NAME: "OpenLayers.Renderer.VML" }); /** * Constant: OpenLayers.Renderer.VML.LABEL_SHIFT * {Object} */ OpenLayers.Renderer.VML.LABEL_SHIFT = { "l": 0, "c": .5, "r": 1, "t": 0, "m": .5, "b": 1 }; /* ====================================================================== OpenLayers/Protocol.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/BaseTypes/Class.js */ /** * Class: OpenLayers.Protocol * Abstract vector layer protocol class. Not to be instantiated directly. Use * one of the protocol subclasses instead. */ OpenLayers.Protocol = OpenLayers.Class({ /** * Property: format * {} The format used by this protocol. */ format: null, /** * Property: options * {Object} Any options sent to the constructor. */ options: null, /** * Property: autoDestroy * {Boolean} The creator of the protocol can set autoDestroy to false * to fully control when the protocol is destroyed. Defaults to * true. */ autoDestroy: true, /** * Property: defaultFilter * {} Optional default filter to read requests */ defaultFilter: null, /** * Constructor: OpenLayers.Protocol * Abstract class for vector protocols. Create instances of a subclass. * * Parameters: * options - {Object} Optional object whose properties will be set on the * instance. */ initialize: function(options) { options = options || {}; OpenLayers.Util.extend(this, options); this.options = options; }, /** * Method: mergeWithDefaultFilter * Merge filter passed to the read method with the default one * * Parameters: * filter - {} */ mergeWithDefaultFilter: function(filter) { var merged; if (filter && this.defaultFilter) { merged = new OpenLayers.Filter.Logical({ type: OpenLayers.Filter.Logical.AND, filters: [this.defaultFilter, filter] }); } else { merged = filter || this.defaultFilter || undefined; } return merged; }, /** * APIMethod: destroy * Clean up the protocol. */ destroy: function() { this.options = null; this.format = null; }, /** * APIMethod: read * Construct a request for reading new features. * * Parameters: * options - {Object} Optional object for configuring the request. * * Returns: * {} An * object, the same object will be passed to the callback function passed * if one exists in the options object. */ read: function(options) { options = options || {}; options.filter = this.mergeWithDefaultFilter(options.filter); }, /** * APIMethod: create * Construct a request for writing newly created features. * * Parameters: * features - {Array({})} or * {} * options - {Object} Optional object for configuring the request. * * Returns: * {} An * object, the same object will be passed to the callback function passed * if one exists in the options object. */ create: function() { }, /** * APIMethod: update * Construct a request updating modified features. * * Parameters: * features - {Array({})} or * {} * options - {Object} Optional object for configuring the request. * * Returns: * {} An * object, the same object will be passed to the callback function passed * if one exists in the options object. */ update: function() { }, /** * APIMethod: delete * Construct a request deleting a removed feature. * * Parameters: * feature - {} * options - {Object} Optional object for configuring the request. * * Returns: * {} An * object, the same object will be passed to the callback function passed * if one exists in the options object. */ "delete": function() { }, /** * APIMethod: commit * Go over the features and for each take action * based on the feature state. Possible actions are create, * update and delete. * * Parameters: * features - {Array({})} * options - {Object} Object whose possible keys are "create", "update", * "delete", "callback" and "scope", the values referenced by the * first three are objects as passed to the "create", "update", and * "delete" methods, the value referenced by the "callback" key is * a function which is called when the commit operation is complete * using the scope referenced by the "scope" key. * * Returns: * {Array({})} An array of * objects. */ commit: function() { }, /** * Method: abort * Abort an ongoing request. * * Parameters: * response - {} */ abort: function(response) { }, /** * Method: createCallback * Returns a function that applies the given public method with resp and * options arguments. * * Parameters: * method - {Function} The method to be applied by the callback. * response - {} The protocol response object. * options - {Object} Options sent to the protocol method */ createCallback: function(method, response, options) { return OpenLayers.Function.bind(function() { method.apply(this, [response, options]); }, this); }, CLASS_NAME: "OpenLayers.Protocol" }); /** * Class: OpenLayers.Protocol.Response * Protocols return Response objects to their users. */ OpenLayers.Protocol.Response = OpenLayers.Class({ /** * Property: code * {Number} - OpenLayers.Protocol.Response.SUCCESS or * OpenLayers.Protocol.Response.FAILURE */ code: null, /** * Property: requestType * {String} The type of request this response corresponds to. Either * "create", "read", "update" or "delete". */ requestType: null, /** * Property: last * {Boolean} - true if this is the last response expected in a commit, * false otherwise, defaults to true. */ last: true, /** * Property: features * {Array({})} or {} * The features returned in the response by the server. Depending on the * protocol's read payload, either features or data will be populated. */ features: null, /** * Property: data * {Object} * The data returned in the response by the server. Depending on the * protocol's read payload, either features or data will be populated. */ data: null, /** * Property: reqFeatures * {Array({})} or {} * The features provided by the user and placed in the request by the * protocol. */ reqFeatures: null, /** * Property: priv */ priv: null, /** * Property: error * {Object} The error object in case a service exception was encountered. */ error: null, /** * Constructor: OpenLayers.Protocol.Response * * Parameters: * options - {Object} Optional object whose properties will be set on the * instance. */ initialize: function(options) { OpenLayers.Util.extend(this, options); }, /** * Method: success * * Returns: * {Boolean} - true on success, false otherwise */ success: function() { return this.code > 0; }, CLASS_NAME: "OpenLayers.Protocol.Response" }); OpenLayers.Protocol.Response.SUCCESS = 1; OpenLayers.Protocol.Response.FAILURE = 0; /* ====================================================================== OpenLayers/Protocol/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 * constructor. * * Inherits from: * - */ 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 , , * , or 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 - {} * 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 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 * class. * * Parameters: * 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 - {} Filter to get serialized as a * query string. * readWithPOST - {Boolean} If the request should be done with POST. * * Returns: * {} 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 - {} 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({})} or * {} * options - {Object} Optional object for configuring the request. * This object is modified and should not be reused. * * Returns: * {} An * 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 is complete. May be overridden * by subclasses. * * Parameters: * resp - {} 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 - {} * options - {Object} Optional object for configuring the request. * This object is modified and should not be reused. * * Returns: * {} An * 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 is complete. May be overridden * by subclasses. * * Parameters: * resp - {} 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 - {} * options - {Object} Optional object for configuring the request. * This object is modified and should not be reused. * * Returns: * {} An * 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 is complete. May be overridden * by subclasses. * * Parameters: * resp - {} 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 - {} 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({})} or * {} 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({})} * options - {Object} Optional object for setting up intermediate commit * callbacks. * * Valid options: * create - {Object} Optional object to be passed to the method. * update - {Object} Optional object to be passed to the method. * delete - {Object} Optional object to be passed to the 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()} 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 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= 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 - {} */ 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 - {} * 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/LayerSwitcher.js ====================================================================== */ /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of 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/Lang.js * @requires OpenLayers/Util.js * @requires OpenLayers/Events/buttonclick.js */ /** * Class: OpenLayers.Control.LayerSwitcher * The LayerSwitcher control displays a table of contents for the map. This * allows the user interface to switch between BaseLasyers and to show or hide * Overlays. By default the switcher is shown minimized on the right edge of * the map, the user may expand it by clicking on the handle. * * To create the LayerSwitcher outside of the map, pass the Id of a html div * as the first argument to the constructor. * * Inherits from: * - */ OpenLayers.Control.LayerSwitcher = OpenLayers.Class(OpenLayers.Control, { /** * Property: layerStates * {Array(Object)} Basically a copy of the "state" of the map's layers * the last time the control was drawn. We have this in order to avoid * unnecessarily redrawing the control. */ layerStates: null, // DOM Elements /** * Property: layersDiv * {DOMElement} */ layersDiv: null, /** * Property: baseLayersDiv * {DOMElement} */ baseLayersDiv: null, /** * Property: baseLayers * {Array(Object)} */ baseLayers: null, /** * Property: dataLbl * {DOMElement} */ dataLbl: null, /** * Property: dataLayersDiv * {DOMElement} */ dataLayersDiv: null, /** * Property: dataLayers * {Array(Object)} */ dataLayers: null, /** * Property: minimizeDiv * {DOMElement} */ minimizeDiv: null, /** * Property: maximizeDiv * {DOMElement} */ maximizeDiv: null, /** * APIProperty: ascending * {Boolean} */ ascending: true, /** * Constructor: OpenLayers.Control.LayerSwitcher * * Parameters: * options - {Object} */ initialize: function(options) { OpenLayers.Control.prototype.initialize.apply(this, arguments); this.layerStates = []; }, /** * APIMethod: destroy */ destroy: function() { //clear out layers info and unregister their events this.clearLayersArray("base"); this.clearLayersArray("data"); this.map.events.un({ buttonclick: this.onButtonClick, addlayer: this.redraw, changelayer: this.redraw, removelayer: this.redraw, changebaselayer: this.redraw, scope: this }); this.events.unregister("buttonclick", this, this.onButtonClick); OpenLayers.Control.prototype.destroy.apply(this, arguments); }, /** * Method: setMap * * Properties: * map - {} */ setMap: function(map) { OpenLayers.Control.prototype.setMap.apply(this, arguments); this.map.events.on({ addlayer: this.redraw, changelayer: this.redraw, removelayer: this.redraw, changebaselayer: this.redraw, scope: this }); if (this.outsideViewport) { this.events.attachToElement(this.div); this.events.register("buttonclick", this, this.onButtonClick); } else { this.map.events.register("buttonclick", this, this.onButtonClick); } }, /** * Method: draw * * Returns: * {DOMElement} A reference to the DIV DOMElement containing the * switcher tabs. */ draw: function() { OpenLayers.Control.prototype.draw.apply(this); // create layout divs this.loadContents(); // set mode to minimize if(!this.outsideViewport) { this.minimizeControl(); } // populate div with current info this.redraw(); return this.div; }, /** * Method: onButtonClick * * Parameters: * evt - {Event} */ onButtonClick: function(evt) { var button = evt.buttonElement; if (button === this.minimizeDiv) { this.minimizeControl(); } else if (button === this.maximizeDiv) { this.maximizeControl(); } else if (button._layerSwitcher === this.id) { if (button["for"]) { button = document.getElementById(button["for"]); } if (!button.disabled) { if (button.type == "radio") { button.checked = true; this.map.setBaseLayer(this.map.getLayer(button._layer)); } else { button.checked = !button.checked; this.updateMap(); } } } }, /** * Method: clearLayersArray * User specifies either "base" or "data". we then clear all the * corresponding listeners, the div, and reinitialize a new array. * * Parameters: * layersType - {String} */ clearLayersArray: function(layersType) { this[layersType + "LayersDiv"].innerHTML = ""; this[layersType + "Layers"] = []; }, /** * Method: checkRedraw * Checks if the layer state has changed since the last redraw() call. * * Returns: * {Boolean} The layer state changed since the last redraw() call. */ checkRedraw: function() { if ( !this.layerStates.length || (this.map.layers.length != this.layerStates.length) ) { return true; } for (var i = 0, len = this.layerStates.length; i < len; i++) { var layerState = this.layerStates[i]; var layer = this.map.layers[i]; if ( (layerState.name != layer.name) || (layerState.inRange != layer.inRange) || (layerState.id != layer.id) || (layerState.visibility != layer.visibility) ) { return true; } } return false; }, /** * Method: redraw * Goes through and takes the current state of the Map and rebuilds the * control to display that state. Groups base layers into a * radio-button group and lists each data layer with a checkbox. * * Returns: * {DOMElement} A reference to the DIV DOMElement containing the control */ redraw: function() { //if the state hasn't changed since last redraw, no need // to do anything. Just return the existing div. if (!this.checkRedraw()) { return this.div; } //clear out previous layers this.clearLayersArray("base"); this.clearLayersArray("data"); var containsOverlays = false; var containsBaseLayers = false; // Save state -- for checking layer if the map state changed. // We save this before redrawing, because in the process of redrawing // we will trigger more visibility changes, and we want to not redraw // and enter an infinite loop. var len = this.map.layers.length; this.layerStates = new Array(len); for (var i=0; i labelSpan["for"] = inputElem.id; OpenLayers.Element.addClass(labelSpan, "labelSpan olButton"); labelSpan._layer = layer.id; labelSpan._layerSwitcher = this.id; if (!baseLayer && !layer.inRange) { labelSpan.style.color = "gray"; } labelSpan.innerHTML = layer.name; labelSpan.style.verticalAlign = (baseLayer) ? "bottom" : "baseline"; // create line break var br = document.createElement("br"); var groupArray = (baseLayer) ? this.baseLayers : this.dataLayers; groupArray.push({ 'layer': layer, 'inputElem': inputElem, 'labelSpan': labelSpan }); var groupDiv = (baseLayer) ? this.baseLayersDiv : this.dataLayersDiv; groupDiv.appendChild(inputElem); groupDiv.appendChild(labelSpan); groupDiv.appendChild(br); } } // if no overlays, dont display the overlay label this.dataLbl.style.display = (containsOverlays) ? "" : "none"; // if no baselayers, dont display the baselayer label this.baseLbl.style.display = (containsBaseLayers) ? "" : "none"; return this.div; }, /** * Method: updateMap * Cycles through the loaded data and base layer input arrays and makes * the necessary calls to the Map object such that that the map's * visual state corresponds to what the user has selected in * the control. */ updateMap: function() { // set the newly selected base layer for(var i=0, len=this.baseLayers.length; i