Skip to content

Instantly share code, notes, and snippets.

@davea
Created September 9, 2019 14:06
Show Gist options
  • Save davea/dc55e667bb539ac6799ff5b5c7cafc73 to your computer and use it in GitHub Desktop.
Save davea/dc55e667bb539ac6799ff5b5c7cafc73 to your computer and use it in GitHub Desktop.
This file has been truncated, but you can view the full file.
/*
OpenLayers.js -- OpenLayers Map Viewer Library
Copyright (c) 2006-2015 by OpenLayers Contributors
Published under the 2-clause BSD license.
See https://raw.githubusercontent.com/openlayers/ol2/master/license.txt for the full text of the license, and https://raw.githubusercontent.com/openlayers/ol2/master/authors.txt for full list of contributors.
Includes compressed code under the following licenses:
(For uncompressed versions of the code used, please see the
OpenLayers Github repository: <https://github.com/openlayers/ol2>)
*/
/**
* Contains XMLHttpRequest.js <http://code.google.com/p/xmlhttprequest/>
* Copyright 2007 Sergey Ilinsky (http://www.ilinsky.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*/
/**
* OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is
* Copyright (c) 2006, Yahoo! Inc.
* All rights reserved.
*
* Redistribution and use of this software in source and binary forms, with or
* without modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Yahoo! Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission of Yahoo! Inc.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/* ======================================================================
OpenLayers/SingleFile.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of 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.14 dev",
/**
* Constant: singleFile
* TODO: remove this in 3.0 when we stop supporting build profiles that
* include OpenLayers.js
*/
singleFile: true,
/**
* Method: _getScriptLocation
* Return the path to this script. This is also implemented in
* OpenLayers.js
*
* Returns:
* {String} Path to this script
*/
_getScriptLocation: (function() {
var r = new RegExp("(^|(.*?\\/))(OpenLayers[^\\/]*?\\.js)(\\?|$)"),
s = document.getElementsByTagName('script'),
src, m, l = "";
for(var i=0, len=s.length; i<len; i++) {
src = s[i].getAttribute('src');
if(src) {
m = src.match(r);
if(m) {
l = m[1];
break;
}
}
}
return (function() { return l; });
})(),
/**
* Property: ImgPath
* {String} Set this to the path where control images are stored, a path
* given here must end with a slash. If set to '' (which is the default)
* OpenLayers will use its script location + "img/".
*
* You will need to set this property when you have a singlefile build of
* OpenLayers that either is not named "OpenLayers.js" or if you move
* the file in a way such that the image directory cannot be derived from
* the script location.
*
* If your custom OpenLayers build is named "my-custom-ol.js" and the images
* of OpenLayers are in a folder "/resources/external/images/ol" a correct
* way of including OpenLayers in your HTML would be:
*
* (code)
* <script src="/path/to/my-custom-ol.js" type="text/javascript"></script>
* <script type="text/javascript">
* // tell OpenLayers where the control images are
* // remember the trailing slash
* OpenLayers.ImgPath = "/resources/external/images/ol/";
* </script>
* (end code)
*
* Please remember that when your OpenLayers script is not named
* "OpenLayers.js" you will have to make sure that the default theme is
* loaded into the page by including an appropriate <link>-tag,
* e.g.:
*
* (code)
* <link rel="stylesheet" href="/path/to/default/style.css" type="text/css">
* (end code)
*/
ImgPath : ''
};
/* ======================================================================
OpenLayers/BaseTypes/Class.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/SingleFile.js
*/
/**
* Constructor: OpenLayers.Class
* Base class used to construct all other classes. Includes support for
* multiple inheritance.
*
* This constructor is new in OpenLayers 2.5. At OpenLayers 3.0, the old
* syntax for creating classes and dealing with inheritance
* will be removed.
*
* To create a new OpenLayers-style class, use the following syntax:
* (code)
* var MyClass = OpenLayers.Class(prototype);
* (end)
*
* To create a new OpenLayers-style class with multiple inheritance, use the
* following syntax:
* (code)
* var MyClass = OpenLayers.Class(Class1, Class2, prototype);
* (end)
*
* Note that instanceof reflection will only reveal Class1 as superclass.
*
*/
OpenLayers.Class = function() {
var len = arguments.length;
var P = arguments[0];
var F = arguments[len-1];
var C = typeof F.initialize == "function" ?
F.initialize :
function(){ P.prototype.initialize.apply(this, arguments); };
if (len > 1) {
var newArgs = [C, P].concat(
Array.prototype.slice.call(arguments).slice(1, len-1), F);
OpenLayers.inherit.apply(null, newArgs);
} else {
C.prototype = F;
}
return C;
};
/**
* Function: OpenLayers.inherit
*
* Parameters:
* C - {Object} the class that inherits
* P - {Object} the superclass to inherit from
*
* In addition to the mandatory C and P parameters, an arbitrary number of
* objects can be passed, which will extend C.
*/
OpenLayers.inherit = function(C, P) {
var F = function() {};
F.prototype = P.prototype;
C.prototype = new F;
var i, l, o;
for(i=2, l=arguments.length; i<l; i++) {
o = arguments[i];
if(typeof o === "function") {
o = o.prototype;
}
OpenLayers.Util.extend(C.prototype, o);
}
};
/**
* APIFunction: extend
* Copy all properties of a source object to a destination object. Modifies
* the passed in destination object. Any properties on the source object
* that are set to undefined will not be (re)set on the destination object.
*
* Parameters:
* destination - {Object} The object that will be modified
* source - {Object} The object with properties to be set on the destination
*
* Returns:
* {Object} The destination object.
*/
OpenLayers.Util = OpenLayers.Util || {};
OpenLayers.Util.extend = function(destination, source) {
destination = destination || {};
if (source) {
for (var property in source) {
var value = source[property];
if (value !== undefined) {
destination[property] = value;
}
}
/**
* IE doesn't include the toString property when iterating over an object's
* properties with the for(property in object) syntax. Explicitly check if
* the source has its own toString property.
*/
/*
* FF/Windows < 2.0.0.13 reports "Illegal operation on WrappedNative
* prototype object" when calling hawOwnProperty if the source object
* is an instance of window.Event.
*/
var sourceIsEvt = typeof window.Event == "function"
&& source instanceof window.Event;
if (!sourceIsEvt
&& source.hasOwnProperty && source.hasOwnProperty("toString")) {
destination.toString = source.toString;
}
}
return destination;
};
/* ======================================================================
OpenLayers/BaseTypes.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/SingleFile.js
*/
/**
* Header: OpenLayers Base Types
* OpenLayers custom string, number and function functions are described here.
*/
/**
* Namespace: OpenLayers.String
* Contains convenience functions for string manipulation.
*/
OpenLayers.String = {
/**
* APIFunction: startsWith
* Test whether a string starts with another string.
*
* Parameters:
* str - {String} The string to test.
* sub - {String} The substring to look for.
*
* Returns:
* {Boolean} The first string starts with the second.
*/
startsWith: function(str, sub) {
return (str.indexOf(sub) == 0);
},
/**
* APIFunction: contains
* Test whether a string contains another string.
*
* Parameters:
* str - {String} The string to test.
* sub - {String} The substring to look for.
*
* Returns:
* {Boolean} The first string contains the second.
*/
contains: function(str, sub) {
return (str.indexOf(sub) != -1);
},
/**
* APIFunction: trim
* Removes leading and trailing whitespace characters from a string.
*
* Parameters:
* str - {String} The (potentially) space padded string. This string is not
* modified.
*
* Returns:
* {String} A trimmed version of the string with all leading and
* trailing spaces removed.
*/
trim: function(str) {
return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
},
/**
* APIFunction: camelize
* Camel-case a hyphenated string.
* Ex. "chicken-head" becomes "chickenHead", and
* "-chicken-head" becomes "ChickenHead".
*
* Parameters:
* str - {String} The string to be camelized. The original is not modified.
*
* Returns:
* {String} The string, camelized
*/
camelize: function(str) {
var oStringList = str.split('-');
var camelizedString = oStringList[0];
for (var i=1, len=oStringList.length; i<len; i++) {
var s = oStringList[i];
camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
}
return camelizedString;
},
/**
* APIFunction: format
* Given a string with tokens in the form ${token}, return a string
* with tokens replaced with properties from the given context
* object. Represent a literal "${" by doubling it, e.g. "${${".
*
* Parameters:
* template - {String} A string with tokens to be replaced. A template
* has the form "literal ${token}" where the token will be replaced
* by the value of context["token"].
* context - {Object} An optional object with properties corresponding
* to the tokens in the format string. If no context is sent, the
* window object will be used.
* args - {Array} Optional arguments to pass to any functions found in
* the context. If a context property is a function, the token
* will be replaced by the return from the function called with
* these arguments.
*
* Returns:
* {String} A string with tokens replaced from the context object.
*/
format: function(template, context, args) {
if(!context) {
context = window;
}
// Example matching:
// str = ${foo.bar}
// match = foo.bar
var replacer = function(str, match) {
var replacement;
// Loop through all subs. Example: ${a.b.c}
// 0 -> replacement = context[a];
// 1 -> replacement = context[a][b];
// 2 -> replacement = context[a][b][c];
var subs = match.split(/\.+/);
for (var i=0; i< subs.length; i++) {
if (i == 0) {
replacement = context;
}
if (replacement === undefined) {
break;
}
replacement = replacement[subs[i]];
}
if(typeof replacement == "function") {
replacement = args ?
replacement.apply(null, args) :
replacement();
}
// If replacement is undefined, return the string 'undefined'.
// This is a workaround for a bugs in browsers not properly
// dealing with non-participating groups in regular expressions:
// http://blog.stevenlevithan.com/archives/npcg-javascript
if (typeof replacement == 'undefined') {
return 'undefined';
} else {
return replacement;
}
};
return template.replace(OpenLayers.String.tokenRegEx, replacer);
},
/**
* Property: tokenRegEx
* Used to find tokens in a string.
* Examples: ${a}, ${a.b.c}, ${a-b}, ${5}
*/
tokenRegEx: /\$\{([\w.]+?)\}/g,
/**
* Property: numberRegEx
* Used to test strings as numbers.
*/
numberRegEx: /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/,
/**
* APIFunction: isNumeric
* Determine whether a string contains only a numeric value.
*
* Examples:
* (code)
* OpenLayers.String.isNumeric("6.02e23") // true
* OpenLayers.String.isNumeric("12 dozen") // false
* OpenLayers.String.isNumeric("4") // true
* OpenLayers.String.isNumeric(" 4 ") // false
* (end)
*
* Returns:
* {Boolean} String contains only a number.
*/
isNumeric: function(value) {
return OpenLayers.String.numberRegEx.test(value);
},
/**
* APIFunction: numericIf
* Converts a string that appears to be a numeric value into a number.
*
* Parameters:
* value - {String}
* trimWhitespace - {Boolean}
*
* Returns:
* {Number|String} a Number if the passed value is a number, a String
* otherwise.
*/
numericIf: function(value, trimWhitespace) {
var originalValue = value;
if (trimWhitespace === true && value != null && value.replace) {
value = value.replace(/^\s*|\s*$/g, "");
}
return OpenLayers.String.isNumeric(value) ? parseFloat(value) : originalValue;
}
};
/**
* Namespace: OpenLayers.Number
* Contains convenience functions for manipulating numbers.
*/
OpenLayers.Number = {
/**
* Property: decimalSeparator
* Decimal separator to use when formatting numbers.
*/
decimalSeparator: ".",
/**
* Property: thousandsSeparator
* Thousands separator to use when formatting numbers.
*/
thousandsSeparator: ",",
/**
* APIFunction: limitSigDigs
* Limit the number of significant digits on a float.
*
* Parameters:
* num - {Float}
* sig - {Integer}
*
* Returns:
* {Float} The number, rounded to the specified number of significant
* digits.
*/
limitSigDigs: function(num, sig) {
var fig = 0;
if (sig > 0) {
fig = parseFloat(num.toPrecision(sig));
}
return fig;
},
/**
* APIFunction: format
* Formats a number for output.
*
* Parameters:
* num - {Float}
* dec - {Integer} Number of decimal places to round to.
* Defaults to 0. Set to null to leave decimal places unchanged.
* tsep - {String} Thousands separator.
* Default is ",".
* dsep - {String} Decimal separator.
* Default is ".".
*
* Returns:
* {String} A string representing the formatted number.
*/
format: function(num, dec, tsep, dsep) {
dec = (typeof dec != "undefined") ? dec : 0;
tsep = (typeof tsep != "undefined") ? tsep :
OpenLayers.Number.thousandsSeparator;
dsep = (typeof dsep != "undefined") ? dsep :
OpenLayers.Number.decimalSeparator;
if (dec != null) {
num = parseFloat(num.toFixed(dec));
}
var parts = num.toString().split(".");
if (parts.length == 1 && dec == null) {
// integer where we do not want to touch the decimals
dec = 0;
}
var integer = parts[0];
if (tsep) {
var thousands = /(-?[0-9]+)([0-9]{3})/;
while(thousands.test(integer)) {
integer = integer.replace(thousands, "$1" + tsep + "$2");
}
}
var str;
if (dec == 0) {
str = integer;
} else {
var rem = parts.length > 1 ? parts[1] : "0";
if (dec != null) {
rem = rem + new Array(dec - rem.length + 1).join("0");
}
str = integer + dsep + rem;
}
return str;
},
/**
* Method: zeroPad
* Create a zero padded string optionally with a radix for casting numbers.
*
* Parameters:
* num - {Number} The number to be zero padded.
* len - {Number} The length of the string to be returned.
* radix - {Number} An integer between 2 and 36 specifying the base to use
* for representing numeric values.
*/
zeroPad: function(num, len, radix) {
var str = num.toString(radix || 10);
while (str.length < len) {
str = "0" + str;
}
return str;
}
};
/**
* Namespace: OpenLayers.Function
* Contains convenience functions for function manipulation.
*/
OpenLayers.Function = {
/**
* APIFunction: bind
* Bind a function to an object. Method to easily create closures with
* 'this' altered.
*
* Parameters:
* func - {Function} Input function.
* object - {Object} The object to bind to the input function (as this).
*
* Returns:
* {Function} A closure with 'this' set to the passed in object.
*/
bind: function(func, object) {
// create a reference to all arguments past the second one
var args = Array.prototype.slice.call(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.call(arguments, 0)
);
return func.apply(object, newArgs);
};
},
/**
* APIFunction: bindAsEventListener
* Bind a function to an object, and configure it to receive the event
* object as first parameter when called.
*
* Parameters:
* func - {Function} Input function to serve as an event listener.
* object - {Object} A reference to this.
*
* Returns:
* {Function}
*/
bindAsEventListener: function(func, object) {
return function(event) {
return func.call(object, event || window.event);
};
},
/**
* APIFunction: False
* A simple function to that just does "return false". We use this to
* avoid attaching anonymous functions to DOM event handlers, which
* causes "issues" on IE<8.
*
* Usage:
* document.onclick = OpenLayers.Function.False;
*
* Returns:
* {Boolean}
*/
False : function() {
return false;
},
/**
* APIFunction: True
* A simple function to that just does "return true". We use this to
* avoid attaching anonymous functions to DOM event handlers, which
* causes "issues" on IE<8.
*
* Usage:
* document.onclick = OpenLayers.Function.True;
*
* Returns:
* {Boolean}
*/
True : function() {
return true;
},
/**
* APIFunction: Void
* A reusable function that returns ``undefined``.
*
* Returns:
* {undefined}
*/
Void: function() {}
};
/**
* Namespace: OpenLayers.Array
* Contains convenience functions for array manipulation.
*/
OpenLayers.Array = {
/**
* APIMethod: filter
* Filter an array. Provides the functionality of the
* Array.prototype.filter extension to the ECMA-262 standard. Where
* available, Array.prototype.filter will be used.
*
* Based on well known example from http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/filter
*
* Parameters:
* array - {Array} The array to be filtered. This array is not mutated.
* Elements added to this array by the callback will not be visited.
* callback - {Function} A function that is called for each element in
* the array. If this function returns true, the element will be
* included in the return. The function will be called with three
* arguments: the element in the array, the index of that element, and
* the array itself. If the optional caller parameter is specified
* the callback will be called with this set to caller.
* caller - {Object} Optional object to be set as this when the callback
* is called.
*
* Returns:
* {Array} An array of elements from the passed in array for which the
* callback returns true.
*/
filter: function(array, callback, caller) {
var selected = [];
if (Array.prototype.filter) {
selected = array.filter(callback, caller);
} else {
var len = array.length;
if (typeof callback != "function") {
throw new TypeError();
}
for(var i=0; i<len; i++) {
if (i in array) {
var val = array[i];
if (callback.call(caller, val, i, array)) {
selected.push(val);
}
}
}
}
return selected;
}
};
/* ======================================================================
OpenLayers/BaseTypes/Bounds.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/BaseTypes/Class.js
*/
/**
* Class: OpenLayers.Bounds
* Instances of this class represent bounding boxes. Data stored as left,
* bottom, right, top floats. All values are initialized to null, however,
* you should make sure you set them before using the bounds for anything.
*
* Possible use case:
* (code)
* bounds = new OpenLayers.Bounds();
* bounds.extend(new OpenLayers.LonLat(4,5));
* bounds.extend(new OpenLayers.LonLat(5,6));
* bounds.toBBOX(); // returns 4,5,5,6
* (end)
*/
OpenLayers.Bounds = OpenLayers.Class({
/**
* Property: left
* {Number} Minimum horizontal coordinate.
*/
left: null,
/**
* Property: bottom
* {Number} Minimum vertical coordinate.
*/
bottom: null,
/**
* Property: right
* {Number} Maximum horizontal coordinate.
*/
right: null,
/**
* Property: top
* {Number} Maximum vertical coordinate.
*/
top: null,
/**
* Property: centerLonLat
* {<OpenLayers.LonLat>} A cached center location. This should not be
* accessed directly. Use <getCenterLonLat> instead.
*/
centerLonLat: null,
/**
* Constructor: OpenLayers.Bounds
* Construct a new bounds object. Coordinates can either be passed as four
* arguments, or as a single argument.
*
* Parameters (four arguments):
* left - {Number} The left bounds of the box. Note that for width
* calculations, this is assumed to be less than the right value.
* bottom - {Number} The bottom bounds of the box. Note that for height
* calculations, this is assumed to be less than the top value.
* right - {Number} The right bounds.
* top - {Number} The top bounds.
*
* Parameters (single argument):
* bounds - {Array(Number)} [left, bottom, right, top]
*/
initialize: function(left, bottom, right, top) {
if (OpenLayers.Util.isArray(left)) {
top = left[3];
right = left[2];
bottom = left[1];
left = left[0];
}
if (left != null) {
this.left = OpenLayers.Util.toFloat(left);
}
if (bottom != null) {
this.bottom = OpenLayers.Util.toFloat(bottom);
}
if (right != null) {
this.right = OpenLayers.Util.toFloat(right);
}
if (top != null) {
this.top = OpenLayers.Util.toFloat(top);
}
},
/**
* Method: clone
* Create a cloned instance of this bounds.
*
* Returns:
* {<OpenLayers.Bounds>} A fresh copy of the bounds
*/
clone:function() {
return new OpenLayers.Bounds(this.left, this.bottom,
this.right, this.top);
},
/**
* Method: equals
* Test a two bounds for equivalence.
*
* Parameters:
* bounds - {<OpenLayers.Bounds>}
*
* Returns:
* {Boolean} The passed-in bounds object has the same left,
* right, top, bottom components as this. Note that if bounds
* passed in is null, returns false.
*/
equals:function(bounds) {
var equals = false;
if (bounds != null) {
equals = ((this.left == bounds.left) &&
(this.right == bounds.right) &&
(this.top == bounds.top) &&
(this.bottom == bounds.bottom));
}
return equals;
},
/**
* APIMethod: toString
* Returns a string representation of the bounds object.
*
* Returns:
* {String} String representation of bounds object.
*/
toString:function() {
return [this.left, this.bottom, this.right, this.top].join(",");
},
/**
* APIMethod: toArray
* Returns an array representation of the bounds object.
*
* Returns an array of left, bottom, right, top properties, or -- when the
* optional parameter is true -- an array of the bottom, left, top,
* right properties.
*
* Parameters:
* reverseAxisOrder - {Boolean} Should we reverse the axis order?
*
* Returns:
* {Array} array of left, bottom, right, top
*/
toArray: function(reverseAxisOrder) {
if (reverseAxisOrder === true) {
return [this.bottom, this.left, this.top, this.right];
} else {
return [this.left, this.bottom, this.right, this.top];
}
},
/**
* APIMethod: toBBOX
* Returns a boundingbox-string representation of the bounds object.
*
* Parameters:
* decimal - {Integer} How many decimal places in the bbox coords?
* Default is 6
* reverseAxisOrder - {Boolean} Should we reverse the axis order?
*
* Returns:
* {String} Simple String representation of bounds object.
* (e.g. "5,42,10,45")
*/
toBBOX:function(decimal, reverseAxisOrder) {
if (decimal== null) {
decimal = 6;
}
var mult = Math.pow(10, decimal);
var xmin = Math.round(this.left * mult) / mult;
var ymin = Math.round(this.bottom * mult) / mult;
var xmax = Math.round(this.right * mult) / mult;
var ymax = Math.round(this.top * mult) / mult;
if (reverseAxisOrder === true) {
return ymin + "," + xmin + "," + ymax + "," + xmax;
} else {
return xmin + "," + ymin + "," + xmax + "," + ymax;
}
},
/**
* APIMethod: toGeometry
* Create a new polygon geometry based on this bounds.
*
* Returns:
* {<OpenLayers.Geometry.Polygon>} A new polygon with the coordinates
* of this bounds.
*/
toGeometry: function() {
return new OpenLayers.Geometry.Polygon([
new OpenLayers.Geometry.LinearRing([
new OpenLayers.Geometry.Point(this.left, this.bottom),
new OpenLayers.Geometry.Point(this.right, this.bottom),
new OpenLayers.Geometry.Point(this.right, this.top),
new OpenLayers.Geometry.Point(this.left, this.top)
])
]);
},
/**
* APIMethod: getWidth
* Returns the width of the bounds.
*
* Returns:
* {Float} The width of the bounds (right minus left).
*/
getWidth:function() {
return (this.right - this.left);
},
/**
* APIMethod: getHeight
* Returns the height of the bounds.
*
* Returns:
* {Float} The height of the bounds (top minus bottom).
*/
getHeight:function() {
return (this.top - this.bottom);
},
/**
* APIMethod: getSize
* Returns an <OpenLayers.Size> object of the bounds.
*
* Returns:
* {<OpenLayers.Size>} The size of the bounds.
*/
getSize:function() {
return new OpenLayers.Size(this.getWidth(), this.getHeight());
},
/**
* APIMethod: getCenterPixel
* Returns the <OpenLayers.Pixel> object which represents the center of the
* bounds.
*
* Returns:
* {<OpenLayers.Pixel>} The center of the bounds in pixel space.
*/
getCenterPixel:function() {
return new OpenLayers.Pixel( (this.left + this.right) / 2,
(this.bottom + this.top) / 2);
},
/**
* APIMethod: getCenterLonLat
* Returns the <OpenLayers.LonLat> object which represents the center of the
* bounds.
*
* Returns:
* {<OpenLayers.LonLat>} The center of the bounds in map space.
*/
getCenterLonLat:function() {
if(!this.centerLonLat) {
this.centerLonLat = new OpenLayers.LonLat(
(this.left + this.right) / 2, (this.bottom + this.top) / 2
);
}
return this.centerLonLat;
},
/**
* APIMethod: scale
* Scales the bounds around a pixel or lonlat. Note that the new
* bounds may return non-integer properties, even if a pixel
* is passed.
*
* Parameters:
* ratio - {Float}
* origin - {<OpenLayers.Pixel> or <OpenLayers.LonLat>}
* Default is center.
*
* Returns:
* {<OpenLayers.Bounds>} A new bounds that is scaled by ratio
* from origin.
*/
scale: function(ratio, origin){
if(origin == null){
origin = this.getCenterLonLat();
}
var origx,origy;
// get origin coordinates
if(origin.CLASS_NAME == "OpenLayers.LonLat"){
origx = origin.lon;
origy = origin.lat;
} else {
origx = origin.x;
origy = origin.y;
}
var left = (this.left - origx) * ratio + origx;
var bottom = (this.bottom - origy) * ratio + origy;
var right = (this.right - origx) * ratio + origx;
var top = (this.top - origy) * ratio + origy;
return new OpenLayers.Bounds(left, bottom, right, top);
},
/**
* APIMethod: add
* Shifts the coordinates of the bound by the given horizontal and vertical
* deltas.
*
* (start code)
* var bounds = new OpenLayers.Bounds(0, 0, 10, 10);
* bounds.toString();
* // => "0,0,10,10"
*
* bounds.add(-1.5, 4).toString();
* // => "-1.5,4,8.5,14"
* (end)
*
* This method will throw a TypeError if it is passed null as an argument.
*
* Parameters:
* x - {Float} horizontal delta
* y - {Float} vertical delta
*
* Returns:
* {<OpenLayers.Bounds>} A new bounds whose coordinates are the same as
* this, but shifted by the passed-in x and y values.
*/
add:function(x, y) {
if ( (x == null) || (y == null) ) {
throw new TypeError('Bounds.add cannot receive null values');
}
return new OpenLayers.Bounds(this.left + x, this.bottom + y,
this.right + x, this.top + y);
},
/**
* APIMethod: extend
* Extend the bounds to include the <OpenLayers.LonLat>,
* <OpenLayers.Geometry.Point> or <OpenLayers.Bounds> specified.
*
* Please note that this function assumes that left < right and
* bottom < top.
*
* Parameters:
* object - {<OpenLayers.LonLat>, <OpenLayers.Geometry.Point> or
* <OpenLayers.Bounds>} The object to be included in the new bounds
* object.
*/
extend:function(object) {
if (object) {
switch(object.CLASS_NAME) {
case "OpenLayers.LonLat":
this.extendXY(object.lon, object.lat);
break;
case "OpenLayers.Geometry.Point":
this.extendXY(object.x, object.y);
break;
case "OpenLayers.Bounds":
// clear cached center location
this.centerLonLat = null;
if ( (this.left == null) || (object.left < this.left)) {
this.left = object.left;
}
if ( (this.bottom == null) || (object.bottom < this.bottom) ) {
this.bottom = object.bottom;
}
if ( (this.right == null) || (object.right > this.right) ) {
this.right = object.right;
}
if ( (this.top == null) || (object.top > this.top) ) {
this.top = object.top;
}
break;
}
}
},
/**
* APIMethod: extendXY
* Extend the bounds to include the XY coordinate specified.
*
* Parameters:
* x - {number} The X part of the the coordinate.
* y - {number} The Y part of the the coordinate.
*/
extendXY:function(x, y) {
// clear cached center location
this.centerLonLat = null;
if ((this.left == null) || (x < this.left)) {
this.left = x;
}
if ((this.bottom == null) || (y < this.bottom)) {
this.bottom = y;
}
if ((this.right == null) || (x > this.right)) {
this.right = x;
}
if ((this.top == null) || (y > this.top)) {
this.top = y;
}
},
/**
* APIMethod: containsLonLat
* Returns whether the bounds object contains the given <OpenLayers.LonLat>.
*
* Parameters:
* ll - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
* object with a 'lon' and 'lat' properties.
* options - {Object} Optional parameters
*
* Acceptable options:
* inclusive - {Boolean} Whether or not to include the border.
* Default is true.
* worldBounds - {<OpenLayers.Bounds>} If a worldBounds is provided, the
* ll will be considered as contained if it exceeds the world bounds,
* but can be wrapped around the dateline so it is contained by this
* bounds.
*
* Returns:
* {Boolean} The passed-in lonlat is within this bounds.
*/
containsLonLat: function(ll, options) {
if (typeof options === "boolean") {
options = {inclusive: options};
}
options = options || {};
var contains = this.contains(ll.lon, ll.lat, options.inclusive),
worldBounds = options.worldBounds;
if (worldBounds && !contains) {
var worldWidth = worldBounds.getWidth();
var worldCenterX = (worldBounds.left + worldBounds.right) / 2;
var worldsAway = Math.round((ll.lon - worldCenterX) / worldWidth);
contains = this.containsLonLat({
lon: ll.lon - worldsAway * worldWidth,
lat: ll.lat
}, {inclusive: options.inclusive});
}
return contains;
},
/**
* APIMethod: containsPixel
* Returns whether the bounds object contains the given <OpenLayers.Pixel>.
*
* Parameters:
* px - {<OpenLayers.Pixel>}
* inclusive - {Boolean} Whether or not to include the border. Default is
* true.
*
* Returns:
* {Boolean} The passed-in pixel is within this bounds.
*/
containsPixel:function(px, inclusive) {
return this.contains(px.x, px.y, inclusive);
},
/**
* APIMethod: contains
* Returns whether the bounds object contains the given x and y.
*
* Parameters:
* x - {Float}
* y - {Float}
* inclusive - {Boolean} Whether or not to include the border. Default is
* true.
*
* Returns:
* {Boolean} Whether or not the passed-in coordinates are within this
* bounds.
*/
contains:function(x, y, inclusive) {
//set default
if (inclusive == null) {
inclusive = true;
}
if (x == null || y == null) {
return false;
}
x = OpenLayers.Util.toFloat(x);
y = OpenLayers.Util.toFloat(y);
var contains = false;
if (inclusive) {
contains = ((x >= this.left) && (x <= this.right) &&
(y >= this.bottom) && (y <= this.top));
} else {
contains = ((x > this.left) && (x < this.right) &&
(y > this.bottom) && (y < this.top));
}
return contains;
},
/**
* APIMethod: intersectsBounds
* Determine whether the target bounds intersects this bounds. Bounds are
* considered intersecting if any of their edges intersect or if one
* bounds contains the other.
*
* Parameters:
* bounds - {<OpenLayers.Bounds>} The target bounds.
* options - {Object} Optional parameters.
*
* Acceptable options:
* inclusive - {Boolean} Treat coincident borders as intersecting. Default
* is true. If false, bounds that do not overlap but only touch at the
* border will not be considered as intersecting.
* worldBounds - {<OpenLayers.Bounds>} If a worldBounds is provided, two
* bounds will be considered as intersecting if they intersect when
* shifted to within the world bounds. This applies only to bounds that
* cross or are completely outside the world bounds.
*
* Returns:
* {Boolean} The passed-in bounds object intersects this bounds.
*/
intersectsBounds:function(bounds, options) {
if (typeof options === "boolean") {
options = {inclusive: options};
}
options = options || {};
if (options.worldBounds) {
var self = this.wrapDateLine(options.worldBounds);
bounds = bounds.wrapDateLine(options.worldBounds);
} else {
self = this;
}
if (options.inclusive == null) {
options.inclusive = true;
}
var intersects = false;
var mightTouch = (
self.left == bounds.right ||
self.right == bounds.left ||
self.top == bounds.bottom ||
self.bottom == bounds.top
);
// if the two bounds only touch at an edge, and inclusive is false,
// then the bounds don't *really* intersect.
if (options.inclusive || !mightTouch) {
// otherwise, if one of the boundaries even partially contains another,
// inclusive of the edges, then they do intersect.
var inBottom = (
((bounds.bottom >= self.bottom) && (bounds.bottom <= self.top)) ||
((self.bottom >= bounds.bottom) && (self.bottom <= bounds.top))
);
var inTop = (
((bounds.top >= self.bottom) && (bounds.top <= self.top)) ||
((self.top > bounds.bottom) && (self.top < bounds.top))
);
var inLeft = (
((bounds.left >= self.left) && (bounds.left <= self.right)) ||
((self.left >= bounds.left) && (self.left <= bounds.right))
);
var inRight = (
((bounds.right >= self.left) && (bounds.right <= self.right)) ||
((self.right >= bounds.left) && (self.right <= bounds.right))
);
intersects = ((inBottom || inTop) && (inLeft || inRight));
}
// document me
if (options.worldBounds && !intersects) {
var world = options.worldBounds;
var width = world.getWidth();
var selfCrosses = !world.containsBounds(self);
var boundsCrosses = !world.containsBounds(bounds);
if (selfCrosses && !boundsCrosses) {
bounds = bounds.add(-width, 0);
intersects = self.intersectsBounds(bounds, {inclusive: options.inclusive});
} else if (boundsCrosses && !selfCrosses) {
self = self.add(-width, 0);
intersects = bounds.intersectsBounds(self, {inclusive: options.inclusive});
}
}
return intersects;
},
/**
* APIMethod: containsBounds
* Returns whether the bounds object contains the given <OpenLayers.Bounds>.
*
* bounds - {<OpenLayers.Bounds>} The target bounds.
* partial - {Boolean} If any of the target corners is within this bounds
* consider the bounds contained. Default is false. If false, the
* entire target bounds must be contained within this bounds.
* inclusive - {Boolean} Treat shared edges as contained. Default is
* true.
*
* Returns:
* {Boolean} The passed-in bounds object is contained within this bounds.
*/
containsBounds:function(bounds, partial, inclusive) {
if (partial == null) {
partial = false;
}
if (inclusive == null) {
inclusive = true;
}
var bottomLeft = this.contains(bounds.left, bounds.bottom, inclusive);
var bottomRight = this.contains(bounds.right, bounds.bottom, inclusive);
var topLeft = this.contains(bounds.left, bounds.top, inclusive);
var topRight = this.contains(bounds.right, bounds.top, inclusive);
return (partial) ? (bottomLeft || bottomRight || topLeft || topRight)
: (bottomLeft && bottomRight && topLeft && topRight);
},
/**
* APIMethod: determineQuadrant
* Returns the the quadrant ("br", "tr", "tl", "bl") in which the given
* <OpenLayers.LonLat> lies.
*
* Parameters:
* lonlat - {<OpenLayers.LonLat>}
*
* Returns:
* {String} The quadrant ("br" "tr" "tl" "bl") of the bounds in which the
* coordinate lies.
*/
determineQuadrant: function(lonlat) {
var quadrant = "";
var center = this.getCenterLonLat();
quadrant += (lonlat.lat < center.lat) ? "b" : "t";
quadrant += (lonlat.lon < center.lon) ? "l" : "r";
return quadrant;
},
/**
* APIMethod: transform
* Transform the Bounds object from source to dest.
*
* Parameters:
* source - {<OpenLayers.Projection>} Source projection.
* dest - {<OpenLayers.Projection>} Destination projection.
*
* Returns:
* {<OpenLayers.Bounds>} Itself, for use in chaining operations.
*/
transform: function(source, dest) {
// clear cached center location
this.centerLonLat = null;
var ll = OpenLayers.Projection.transform(
{'x': this.left, 'y': this.bottom}, source, dest);
var lr = OpenLayers.Projection.transform(
{'x': this.right, 'y': this.bottom}, source, dest);
var ul = OpenLayers.Projection.transform(
{'x': this.left, 'y': this.top}, source, dest);
var ur = OpenLayers.Projection.transform(
{'x': this.right, 'y': this.top}, source, dest);
this.left = Math.min(ll.x, ul.x);
this.bottom = Math.min(ll.y, lr.y);
this.right = Math.max(lr.x, ur.x);
this.top = Math.max(ul.y, ur.y);
return this;
},
/**
* APIMethod: wrapDateLine
* Wraps the bounds object around the dateline.
*
* Parameters:
* maxExtent - {<OpenLayers.Bounds>}
* options - {Object} Some possible options are:
*
* Allowed Options:
* leftTolerance - {float} Allow for a margin of error
* with the 'left' value of this
* bound.
* Default is 0.
* rightTolerance - {float} Allow for a margin of error
* with the 'right' value of
* this bound.
* Default is 0.
*
* Returns:
* {<OpenLayers.Bounds>} A copy of this bounds, but wrapped around the
* "dateline" (as specified by the borders of
* maxExtent). Note that this function only returns
* a different bounds value if this bounds is
* *entirely* outside of the maxExtent. If this
* bounds straddles the dateline (is part in/part
* out of maxExtent), the returned bounds will always
* cross the left edge of the given maxExtent.
*.
*/
wrapDateLine: function(maxExtent, options) {
options = options || {};
var leftTolerance = options.leftTolerance || 0;
var rightTolerance = options.rightTolerance || 0;
var newBounds = this.clone();
if (maxExtent) {
var width = maxExtent.getWidth();
//shift right?
while (newBounds.left < maxExtent.left &&
newBounds.right - rightTolerance <= maxExtent.left ) {
newBounds = newBounds.add(width, 0);
}
//shift left?
while (newBounds.left + leftTolerance >= maxExtent.right &&
newBounds.right > maxExtent.right ) {
newBounds = newBounds.add(-width, 0);
}
// crosses right only? force left
var newLeft = newBounds.left + leftTolerance;
if (newLeft < maxExtent.right && newLeft > maxExtent.left &&
newBounds.right - rightTolerance > maxExtent.right) {
newBounds = newBounds.add(-width, 0);
}
}
return newBounds;
},
CLASS_NAME: "OpenLayers.Bounds"
});
/**
* APIFunction: fromString
* Alternative constructor that builds a new OpenLayers.Bounds from a
* parameter string.
*
* (begin code)
* OpenLayers.Bounds.fromString("5,42,10,45");
* // => equivalent to ...
* new OpenLayers.Bounds(5, 42, 10, 45);
* (end)
*
* Parameters:
* str - {String} Comma-separated bounds string. (e.g. "5,42,10,45")
* reverseAxisOrder - {Boolean} Does the string use reverse axis order?
*
* Returns:
* {<OpenLayers.Bounds>} New bounds object built from the
* passed-in String.
*/
OpenLayers.Bounds.fromString = function(str, reverseAxisOrder) {
var bounds = str.split(",");
return OpenLayers.Bounds.fromArray(bounds, reverseAxisOrder);
};
/**
* APIFunction: fromArray
* Alternative constructor that builds a new OpenLayers.Bounds from an array.
*
* (begin code)
* OpenLayers.Bounds.fromArray( [5, 42, 10, 45] );
* // => equivalent to ...
* new OpenLayers.Bounds(5, 42, 10, 45);
* (end)
*
* Parameters:
* bbox - {Array(Float)} Array of bounds values (e.g. [5,42,10,45])
* reverseAxisOrder - {Boolean} Does the array use reverse axis order?
*
* Returns:
* {<OpenLayers.Bounds>} New bounds object built from the passed-in Array.
*/
OpenLayers.Bounds.fromArray = function(bbox, reverseAxisOrder) {
return reverseAxisOrder === true ?
new OpenLayers.Bounds(bbox[1], bbox[0], bbox[3], bbox[2]) :
new OpenLayers.Bounds(bbox[0], bbox[1], bbox[2], bbox[3]);
};
/**
* APIFunction: fromSize
* Alternative constructor that builds a new OpenLayers.Bounds from a size.
*
* (begin code)
* OpenLayers.Bounds.fromSize( new OpenLayers.Size(10, 20) );
* // => equivalent to ...
* new OpenLayers.Bounds(0, 20, 10, 0);
* (end)
*
* Parameters:
* size - {<OpenLayers.Size> or Object} <OpenLayers.Size> or an object with
* both 'w' and 'h' properties.
*
* Returns:
* {<OpenLayers.Bounds>} New bounds object built from the passed-in size.
*/
OpenLayers.Bounds.fromSize = function(size) {
return new OpenLayers.Bounds(0,
size.h,
size.w,
0);
};
/**
* Function: oppositeQuadrant
* Get the opposite quadrant for a given quadrant string.
*
* (begin code)
* OpenLayers.Bounds.oppositeQuadrant( "tl" );
* // => "br"
*
* OpenLayers.Bounds.oppositeQuadrant( "tr" );
* // => "bl"
* (end)
*
* Parameters:
* quadrant - {String} two character quadrant shortstring
*
* Returns:
* {String} The opposing quadrant ("br" "tr" "tl" "bl"). For Example, if
* you pass in "bl" it returns "tr", if you pass in "br" it
* returns "tl", etc.
*/
OpenLayers.Bounds.oppositeQuadrant = function(quadrant) {
var opp = "";
opp += (quadrant.charAt(0) == 't') ? 'b' : 't';
opp += (quadrant.charAt(1) == 'l') ? 'r' : 'l';
return opp;
};
/* ======================================================================
OpenLayers/BaseTypes/Element.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Util.js
* @requires OpenLayers/BaseTypes.js
*/
/**
* Namespace: OpenLayers.Element
*/
OpenLayers.Element = {
/**
* APIFunction: visible
*
* Parameters:
* element - {DOMElement}
*
* Returns:
* {Boolean} Is the element visible?
*/
visible: function(element) {
return OpenLayers.Util.getElement(element).style.display != 'none';
},
/**
* APIFunction: toggle
* Toggle the visibility of element(s) passed in
*
* Parameters:
* element - {DOMElement} Actually user can pass any number of elements
*/
toggle: function() {
for (var i=0, len=arguments.length; i<len; i++) {
var element = OpenLayers.Util.getElement(arguments[i]);
var display = OpenLayers.Element.visible(element) ? 'none'
: '';
element.style.display = display;
}
},
/**
* APIFunction: remove
* Remove the specified element from the DOM.
*
* Parameters:
* element - {DOMElement}
*/
remove: function(element) {
element = OpenLayers.Util.getElement(element);
element.parentNode.removeChild(element);
},
/**
* APIFunction: getHeight
*
* Parameters:
* element - {DOMElement}
*
* Returns:
* {Integer} The offset height of the element passed in
*/
getHeight: function(element) {
element = OpenLayers.Util.getElement(element);
return element.offsetHeight;
},
/**
* Function: hasClass
* Tests if an element has the given CSS class name.
*
* Parameters:
* element - {DOMElement} A DOM element node.
* name - {String} The CSS class name to search for.
*
* Returns:
* {Boolean} The element has the given class name.
*/
hasClass: function(element, name) {
var names = element.className;
return (!!names && new RegExp("(^|\\s)" + name + "(\\s|$)").test(names));
},
/**
* Function: addClass
* Add a CSS class name to an element. Safe where element already has
* the class name.
*
* Parameters:
* element - {DOMElement} A DOM element node.
* name - {String} The CSS class name to add.
*
* Returns:
* {DOMElement} The element.
*/
addClass: function(element, name) {
if(!OpenLayers.Element.hasClass(element, name)) {
element.className += (element.className ? " " : "") + name;
}
return element;
},
/**
* Function: removeClass
* Remove a CSS class name from an element. Safe where element does not
* have the class name.
*
* Parameters:
* element - {DOMElement} A DOM element node.
* name - {String} The CSS class name to remove.
*
* Returns:
* {DOMElement} The element.
*/
removeClass: function(element, name) {
var names = element.className;
if(names) {
element.className = OpenLayers.String.trim(
names.replace(
new RegExp("(^|\\s+)" + name + "(\\s+|$)"), " "
)
);
}
return element;
},
/**
* Function: toggleClass
* Remove a CSS class name from an element if it exists. Add the class name
* if it doesn't exist.
*
* Parameters:
* element - {DOMElement} A DOM element node.
* name - {String} The CSS class name to toggle.
*
* Returns:
* {DOMElement} The element.
*/
toggleClass: function(element, name) {
if(OpenLayers.Element.hasClass(element, name)) {
OpenLayers.Element.removeClass(element, name);
} else {
OpenLayers.Element.addClass(element, name);
}
return element;
},
/**
* APIFunction: getStyle
*
* Parameters:
* element - {DOMElement}
* style - {?}
*
* Returns:
* {?}
*/
getStyle: function(element, style) {
element = OpenLayers.Util.getElement(element);
var value = null;
if (element && element.style) {
value = element.style[OpenLayers.String.camelize(style)];
if (!value) {
if (document.defaultView &&
document.defaultView.getComputedStyle) {
var css = document.defaultView.getComputedStyle(element, null);
value = css ? css.getPropertyValue(style) : null;
} else if (element.currentStyle) {
value = element.currentStyle[OpenLayers.String.camelize(style)];
}
}
var positions = ['left', 'top', 'right', 'bottom'];
if (window.opera &&
(OpenLayers.Util.indexOf(positions,style) != -1) &&
(OpenLayers.Element.getStyle(element, 'position') == 'static')) {
value = 'auto';
}
}
return value == 'auto' ? null : value;
}
};
/* ======================================================================
OpenLayers/BaseTypes/LonLat.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/BaseTypes/Class.js
*/
/**
* Class: OpenLayers.LonLat
* This class represents a longitude and latitude pair
*/
OpenLayers.LonLat = OpenLayers.Class({
/**
* APIProperty: lon
* {Float} The x-axis coodinate in map units
*/
lon: 0.0,
/**
* APIProperty: lat
* {Float} The y-axis coordinate in map units
*/
lat: 0.0,
/**
* Constructor: OpenLayers.LonLat
* Create a new map location. Coordinates can be passed either as two
* arguments, or as a single argument.
*
* Parameters (two arguments):
* lon - {Number} The x-axis coordinate in map units. If your map is in
* a geographic projection, this will be the Longitude. Otherwise,
* it will be the x coordinate of the map location in your map units.
* lat - {Number} The y-axis coordinate in map units. If your map is in
* a geographic projection, this will be the Latitude. Otherwise,
* it will be the y coordinate of the map location in your map units.
*
* Parameters (single argument):
* location - {Array(Float)} [lon, lat]
*/
initialize: function(lon, lat) {
if (OpenLayers.Util.isArray(lon)) {
lat = lon[1];
lon = lon[0];
}
this.lon = OpenLayers.Util.toFloat(lon);
this.lat = OpenLayers.Util.toFloat(lat);
},
/**
* Method: toString
* Return a readable string version of the lonlat
*
* Returns:
* {String} String representation of OpenLayers.LonLat object.
* (e.g. <i>"lon=5,lat=42"</i>)
*/
toString:function() {
return ("lon=" + this.lon + ",lat=" + this.lat);
},
/**
* APIMethod: toShortString
*
* Returns:
* {String} Shortened String representation of OpenLayers.LonLat object.
* (e.g. <i>"5, 42"</i>)
*/
toShortString:function() {
return (this.lon + ", " + this.lat);
},
/**
* APIMethod: clone
*
* Returns:
* {<OpenLayers.LonLat>} New OpenLayers.LonLat object with the same lon
* and lat values
*/
clone:function() {
return new OpenLayers.LonLat(this.lon, this.lat);
},
/**
* APIMethod: add
*
* Parameters:
* lon - {Float}
* lat - {Float}
*
* Returns:
* {<OpenLayers.LonLat>} A new OpenLayers.LonLat object with the lon and
* lat passed-in added to this's.
*/
add:function(lon, lat) {
if ( (lon == null) || (lat == null) ) {
throw new TypeError('LonLat.add cannot receive null values');
}
return new OpenLayers.LonLat(this.lon + OpenLayers.Util.toFloat(lon),
this.lat + OpenLayers.Util.toFloat(lat));
},
/**
* APIMethod: equals
*
* Parameters:
* ll - {<OpenLayers.LonLat>}
*
* Returns:
* {Boolean} Boolean value indicating whether the passed-in
* <OpenLayers.LonLat> object has the same lon and lat
* components as this.
* Note: if ll passed in is null, returns false
*/
equals:function(ll) {
var equals = false;
if (ll != null) {
equals = ((this.lon == ll.lon && this.lat == ll.lat) ||
(isNaN(this.lon) && isNaN(this.lat) && isNaN(ll.lon) && isNaN(ll.lat)));
}
return equals;
},
/**
* APIMethod: transform
* Transform the LonLat object from source to dest. This transformation is
* *in place*: if you want a *new* lonlat, use .clone() first.
*
* Parameters:
* source - {<OpenLayers.Projection>} Source projection.
* dest - {<OpenLayers.Projection>} Destination projection.
*
* Returns:
* {<OpenLayers.LonLat>} Itself, for use in chaining operations.
*/
transform: function(source, dest) {
var point = OpenLayers.Projection.transform(
{'x': this.lon, 'y': this.lat}, source, dest);
this.lon = point.x;
this.lat = point.y;
return this;
},
/**
* APIMethod: wrapDateLine
*
* Parameters:
* maxExtent - {<OpenLayers.Bounds>}
*
* Returns:
* {<OpenLayers.LonLat>} A copy of this lonlat, but wrapped around the
* "dateline" (as specified by the borders of
* maxExtent)
*/
wrapDateLine: function(maxExtent) {
var newLonLat = this.clone();
if (maxExtent) {
//shift right?
while (newLonLat.lon < maxExtent.left) {
newLonLat.lon += maxExtent.getWidth();
}
//shift left?
while (newLonLat.lon > maxExtent.right) {
newLonLat.lon -= maxExtent.getWidth();
}
}
return newLonLat;
},
CLASS_NAME: "OpenLayers.LonLat"
});
/**
* Function: fromString
* Alternative constructor that builds a new <OpenLayers.LonLat> from a
* parameter string
*
* Parameters:
* str - {String} Comma-separated Lon,Lat coordinate string.
* (e.g. <i>"5,40"</i>)
*
* Returns:
* {<OpenLayers.LonLat>} New <OpenLayers.LonLat> object built from the
* passed-in String.
*/
OpenLayers.LonLat.fromString = function(str) {
var pair = str.split(",");
return new OpenLayers.LonLat(pair[0], pair[1]);
};
/**
* Function: fromArray
* Alternative constructor that builds a new <OpenLayers.LonLat> from an
* array of two numbers that represent lon- and lat-values.
*
* Parameters:
* arr - {Array(Float)} Array of lon/lat values (e.g. [5,-42])
*
* Returns:
* {<OpenLayers.LonLat>} New <OpenLayers.LonLat> object built from the
* passed-in array.
*/
OpenLayers.LonLat.fromArray = function(arr) {
var gotArr = OpenLayers.Util.isArray(arr),
lon = gotArr && arr[0],
lat = gotArr && arr[1];
return new OpenLayers.LonLat(lon, lat);
};
/* ======================================================================
OpenLayers/BaseTypes/Pixel.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/BaseTypes/Class.js
*/
/**
* Class: OpenLayers.Pixel
* This class represents a screen coordinate, in x and y coordinates
*/
OpenLayers.Pixel = OpenLayers.Class({
/**
* APIProperty: x
* {Number} The x coordinate
*/
x: 0.0,
/**
* APIProperty: y
* {Number} The y coordinate
*/
y: 0.0,
/**
* Constructor: OpenLayers.Pixel
* Create a new OpenLayers.Pixel instance
*
* Parameters:
* x - {Number} The x coordinate
* y - {Number} The y coordinate
*
* Returns:
* An instance of OpenLayers.Pixel
*/
initialize: function(x, y) {
this.x = parseFloat(x);
this.y = parseFloat(y);
},
/**
* Method: toString
* Cast this object into a string
*
* Returns:
* {String} The string representation of Pixel. ex: "x=200.4,y=242.2"
*/
toString:function() {
return ("x=" + this.x + ",y=" + this.y);
},
/**
* APIMethod: clone
* Return a clone of this pixel object
*
* Returns:
* {<OpenLayers.Pixel>} A clone pixel
*/
clone:function() {
return new OpenLayers.Pixel(this.x, this.y);
},
/**
* APIMethod: equals
* Determine whether one pixel is equivalent to another
*
* Parameters:
* px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object with
* a 'x' and 'y' properties.
*
* Returns:
* {Boolean} The point passed in as parameter is equal to this. Note that
* if px passed in is null, returns false.
*/
equals:function(px) {
var equals = false;
if (px != null) {
equals = ((this.x == px.x && this.y == px.y) ||
(isNaN(this.x) && isNaN(this.y) && isNaN(px.x) && isNaN(px.y)));
}
return equals;
},
/**
* APIMethod: distanceTo
* Returns the distance to the pixel point passed in as a parameter.
*
* Parameters:
* px - {<OpenLayers.Pixel>}
*
* Returns:
* {Float} The pixel point passed in as parameter to calculate the
* distance to.
*/
distanceTo:function(px) {
return Math.sqrt(
Math.pow(this.x - px.x, 2) +
Math.pow(this.y - px.y, 2)
);
},
/**
* APIMethod: add
*
* Parameters:
* x - {Integer}
* y - {Integer}
*
* Returns:
* {<OpenLayers.Pixel>} A new Pixel with this pixel's x&y augmented by the
* values passed in.
*/
add:function(x, y) {
if ( (x == null) || (y == null) ) {
throw new TypeError('Pixel.add cannot receive null values');
}
return new OpenLayers.Pixel(this.x + x, this.y + y);
},
/**
* APIMethod: offset
*
* Parameters
* px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object with
* a 'x' and 'y' properties.
*
* Returns:
* {<OpenLayers.Pixel>} A new Pixel with this pixel's x&y augmented by the
* x&y values of the pixel passed in.
*/
offset:function(px) {
var newPx = this.clone();
if (px) {
newPx = this.add(px.x, px.y);
}
return newPx;
},
CLASS_NAME: "OpenLayers.Pixel"
});
/* ======================================================================
OpenLayers/BaseTypes/Size.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/BaseTypes/Class.js
*/
/**
* Class: OpenLayers.Size
* Instances of this class represent a width/height pair
*/
OpenLayers.Size = OpenLayers.Class({
/**
* APIProperty: w
* {Number} width
*/
w: 0.0,
/**
* APIProperty: h
* {Number} height
*/
h: 0.0,
/**
* Constructor: OpenLayers.Size
* Create an instance of OpenLayers.Size
*
* Parameters:
* w - {Number} width
* h - {Number} height
*/
initialize: function(w, h) {
this.w = parseFloat(w);
this.h = parseFloat(h);
},
/**
* Method: toString
* Return the string representation of a size object
*
* Returns:
* {String} The string representation of OpenLayers.Size object.
* (e.g. <i>"w=55,h=66"</i>)
*/
toString:function() {
return ("w=" + this.w + ",h=" + this.h);
},
/**
* APIMethod: clone
* Create a clone of this size object
*
* Returns:
* {<OpenLayers.Size>} A new OpenLayers.Size object with the same w and h
* values
*/
clone:function() {
return new OpenLayers.Size(this.w, this.h);
},
/**
*
* APIMethod: equals
* Determine where this size is equal to another
*
* Parameters:
* sz - {<OpenLayers.Size>|Object} An OpenLayers.Size or an object with
* a 'w' and 'h' properties.
*
* Returns:
* {Boolean} The passed in size has the same h and w properties as this one.
* Note that if sz passed in is null, returns false.
*/
equals:function(sz) {
var equals = false;
if (sz != null) {
equals = ((this.w == sz.w && this.h == sz.h) ||
(isNaN(this.w) && isNaN(this.h) && isNaN(sz.w) && isNaN(sz.h)));
}
return equals;
},
CLASS_NAME: "OpenLayers.Size"
});
/* ======================================================================
OpenLayers/Console.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published 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 extension and Firebug Lite.
* Most notably, the Firebug Lite console does not currently allow for
* hyperlinks to code or for clicking on object to explore their properties.
*
*/
OpenLayers.Console = {
/**
* Create empty functions for all console methods. The real value of these
* properties will be set if Firebug Lite (../Firebug/firebug.js script) is
* included. We explicitly require the Firebug Lite script to trigger
* functionality of the OpenLayers.Console methods.
*/
/**
* APIFunction: log
* Log an object in the console. The Firebug Lite console logs string
* representation of objects. Given multiple arguments, they will
* be cast to strings and logged with a space delimiter. If the first
* argument is a string with printf-like formatting, subsequent arguments
* will be used in string substitution. Any additional arguments (beyond
* the number substituted in a format string) will be appended in a space-
* delimited line.
*
* Parameters:
* object - {Object}
*/
log: function() {},
/**
* APIFunction: debug
* Writes a message to the console, including a hyperlink to the line
* where it was called.
*
* May be called with multiple arguments as with OpenLayers.Console.log().
*
* Parameters:
* object - {Object}
*/
debug: function() {},
/**
* APIFunction: info
* Writes a message to the console with the visual "info" icon and color
* coding and a hyperlink to the line where it was called.
*
* May be called with multiple arguments as with OpenLayers.Console.log().
*
* Parameters:
* object - {Object}
*/
info: function() {},
/**
* APIFunction: warn
* Writes a message to the console with the visual "warning" icon and
* color coding and a hyperlink to the line where it was called.
*
* May be called with multiple arguments as with OpenLayers.Console.log().
*
* Parameters:
* object - {Object}
*/
warn: function() {},
/**
* APIFunction: error
* Writes a message to the console with the visual "error" icon and color
* coding and a hyperlink to the line where it was called.
*
* May be called with multiple arguments as with OpenLayers.Console.log().
*
* Parameters:
* object - {Object}
*/
error: function() {},
/**
* APIFunction: userError
* A single interface for showing error messages to the user. The default
* behavior is a Javascript alert, though this can be overridden by
* reassigning OpenLayers.Console.userError to a different function.
*
* Expects a single error message
*
* Parameters:
* error - {Object}
*/
userError: function(error) {
alert(error);
},
/**
* APIFunction: assert
* Tests that an expression is true. If not, it will write a message to
* the console and throw an exception.
*
* May be called with multiple arguments as with OpenLayers.Console.log().
*
* Parameters:
* object - {Object}
*/
assert: function() {},
/**
* APIFunction: dir
* Prints an interactive listing of all properties of the object. This
* looks identical to the view that you would see in the DOM tab.
*
* Parameters:
* object - {Object}
*/
dir: function() {},
/**
* APIFunction: dirxml
* Prints the XML source tree of an HTML or XML element. This looks
* identical to the view that you would see in the HTML tab. You can click
* on any node to inspect it in the HTML tab.
*
* Parameters:
* object - {Object}
*/
dirxml: function() {},
/**
* APIFunction: trace
* Prints an interactive stack trace of JavaScript execution at the point
* where it is called. The stack trace details the functions on the stack,
* as well as the values that were passed as arguments to each function.
* You can click each function to take you to its source in the Script tab,
* and click each argument value to inspect it in the DOM or HTML tabs.
*
*/
trace: function() {},
/**
* APIFunction: group
* Writes a message to the console and opens a nested block to indent all
* future messages sent to the console. Call OpenLayers.Console.groupEnd()
* to close the block.
*
* May be called with multiple arguments as with OpenLayers.Console.log().
*
* Parameters:
* object - {Object}
*/
group: function() {},
/**
* APIFunction: groupEnd
* Closes the most recently opened block created by a call to
* OpenLayers.Console.group
*/
groupEnd: function() {},
/**
* APIFunction: time
* Creates a new timer under the given name. Call
* OpenLayers.Console.timeEnd(name)
* with the same name to stop the timer and print the time elapsed.
*
* Parameters:
* name - {String}
*/
time: function() {},
/**
* APIFunction: timeEnd
* Stops a timer created by a call to OpenLayers.Console.time(name) and
* writes the time elapsed.
*
* Parameters:
* name - {String}
*/
timeEnd: function() {},
/**
* APIFunction: profile
* Turns on the JavaScript profiler. The optional argument title would
* contain the text to be printed in the header of the profile report.
*
* This function is not currently implemented in Firebug Lite.
*
* Parameters:
* title - {String} Optional title for the profiler
*/
profile: function() {},
/**
* APIFunction: profileEnd
* Turns off the JavaScript profiler and prints its report.
*
* This function is not currently implemented in Firebug Lite.
*/
profileEnd: function() {},
/**
* APIFunction: count
* Writes the number of times that the line of code where count was called
* was executed. The optional argument title will print a message in
* addition to the number of the count.
*
* This function is not currently implemented in Firebug Lite.
*
* Parameters:
* title - {String} Optional title to be printed with count
*/
count: function() {},
CLASS_NAME: "OpenLayers.Console"
};
/**
* Execute an anonymous function to extend the OpenLayers.Console namespace
* if the firebug.js script is included. This closure is used so that the
* "scripts" and "i" variables don't pollute the global namespace.
*/
(function() {
/**
* If Firebug Lite is included (before this script), re-route all
* OpenLayers.Console calls to the console object.
*/
var scripts = document.getElementsByTagName("script");
for(var i=0, len=scripts.length; i<len; ++i) {
if(scripts[i].src.indexOf("firebug.js") != -1) {
if(console) {
OpenLayers.Util.extend(OpenLayers.Console, console);
break;
}
}
}
})();
/* ======================================================================
OpenLayers/Lang.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/BaseTypes.js
* @requires OpenLayers/Console.js
*/
/**
* Namespace: OpenLayers.Lang
* Internationalization namespace. Contains dictionaries in various languages
* and methods to set and get the current language.
*/
OpenLayers.Lang = {
/**
* Property: code
* {String} Current language code to use in OpenLayers. Use the
* <setCode> method to set this value and the <getCode> method to
* retrieve it.
*/
code: null,
/**
* APIProperty: defaultCode
* {String} Default language to use when a specific language can't be
* found. Default is "en".
*/
defaultCode: "en",
/**
* APIFunction: getCode
* Get the current language code.
*
* Returns:
* {String} The current language code.
*/
getCode: function() {
if(!OpenLayers.Lang.code) {
OpenLayers.Lang.setCode();
}
return OpenLayers.Lang.code;
},
/**
* APIFunction: setCode
* Set the language code for string translation. This code is used by
* the <OpenLayers.Lang.translate> method.
*
* Parameters:
* code - {String} These codes follow the IETF recommendations at
* http://www.ietf.org/rfc/rfc3066.txt. If no value is set, the
* browser's language setting will be tested. If no <OpenLayers.Lang>
* dictionary exists for the code, the <OpenLayers.String.defaultLang>
* will be used.
*/
setCode: function(code) {
var lang;
if(!code) {
code = (OpenLayers.BROWSER_NAME == "msie") ?
navigator.userLanguage : navigator.language;
}
var parts = code.split('-');
parts[0] = parts[0].toLowerCase();
if(typeof OpenLayers.Lang[parts[0]] == "object") {
lang = parts[0];
}
// check for regional extensions
if(parts[1]) {
var testLang = parts[0] + '-' + parts[1].toUpperCase();
if(typeof OpenLayers.Lang[testLang] == "object") {
lang = testLang;
}
}
if(!lang) {
OpenLayers.Console.warn(
'Failed to find OpenLayers.Lang.' + parts.join("-") +
' dictionary, falling back to default language'
);
lang = OpenLayers.Lang.defaultCode;
}
OpenLayers.Lang.code = lang;
},
/**
* APIMethod: translate
* Looks up a key from a dictionary based on the current language string.
* The value of <getCode> will be used to determine the appropriate
* dictionary. Dictionaries are stored in <OpenLayers.Lang>.
*
* Parameters:
* key - {String} The key for an i18n string value in the dictionary.
* context - {Object} Optional context to be used with
* <OpenLayers.String.format>.
*
* Returns:
* {String} A internationalized string.
*/
translate: function(key, context) {
var dictionary = OpenLayers.Lang[OpenLayers.Lang.getCode()];
var message = dictionary && dictionary[key];
if(!message) {
// Message not found, fall back to message key
message = key;
}
if(context) {
message = OpenLayers.String.format(message, context);
}
return message;
}
};
/**
* APIMethod: OpenLayers.i18n
* Alias for <OpenLayers.Lang.translate>. Looks up a key from a dictionary
* based on the current language string. The value of
* <OpenLayers.Lang.getCode> will be used to determine the appropriate
* dictionary. Dictionaries are stored in <OpenLayers.Lang>.
*
* Parameters:
* key - {String} The key for an i18n string value in the dictionary.
* context - {Object} Optional context to be used with
* <OpenLayers.String.format>.
*
* Returns:
* {String} A internationalized string.
*/
OpenLayers.i18n = OpenLayers.Lang.translate;
/* ======================================================================
OpenLayers/Util.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/BaseTypes.js
* @requires OpenLayers/BaseTypes/Bounds.js
* @requires OpenLayers/BaseTypes/Element.js
* @requires OpenLayers/BaseTypes/LonLat.js
* @requires OpenLayers/BaseTypes/Pixel.js
* @requires OpenLayers/BaseTypes/Size.js
* @requires OpenLayers/Lang.js
*/
/**
* Namespace: Util
*/
OpenLayers.Util = OpenLayers.Util || {};
/**
* Function: getElement
* This is the old $() from prototype
*
* Parameters:
* e - {String or DOMElement or Window}
*
* Returns:
* {Array(DOMElement) or DOMElement}
*/
OpenLayers.Util.getElement = function() {
var elements = [];
for (var i=0, len=arguments.length; i<len; i++) {
var element = arguments[i];
if (typeof element == 'string') {
element = document.getElementById(element);
}
if (arguments.length == 1) {
return element;
}
elements.push(element);
}
return elements;
};
/**
* Function: isElement
* A cross-browser implementation of "e instanceof Element".
*
* Parameters:
* o - {Object} The object to test.
*
* Returns:
* {Boolean}
*/
OpenLayers.Util.isElement = function(o) {
return !!(o && o.nodeType === 1);
};
/**
* Function: isArray
* Tests that the provided object is an array.
* This test handles the cross-IFRAME case not caught
* by "a instanceof Array" and should be used instead.
*
* Parameters:
* a - {Object} the object test.
*
* Returns:
* {Boolean} true if the object is an array.
*/
OpenLayers.Util.isArray = function(a) {
return (Object.prototype.toString.call(a) === '[object Array]');
};
/**
* Function: removeItem
* Remove an object from an array. Iterates through the array
* to find the item, then removes it.
*
* Parameters:
* array - {Array}
* item - {Object}
*
* Returns:
* {Array} A reference to the array
*/
OpenLayers.Util.removeItem = function(array, item) {
for(var i = array.length - 1; i >= 0; i--) {
if(array[i] == item) {
array.splice(i,1);
//break;more than once??
}
}
return array;
};
/**
* Function: indexOf
* Seems to exist already in FF, but not in MOZ.
*
* Parameters:
* array - {Array}
* obj - {*}
*
* Returns:
* {Integer} The index at which the first object was found in the array.
* If not found, returns -1.
*/
OpenLayers.Util.indexOf = function(array, obj) {
// use the build-in function if available.
if (typeof array.indexOf == "function") {
return array.indexOf(obj);
} else {
for (var i = 0, len = array.length; i < len; i++) {
if (array[i] == obj) {
return i;
}
}
return -1;
}
};
/**
* Property: dotless
* {RegExp}
* Compiled regular expression to match dots ("."). This is used for replacing
* dots in identifiers. Because object identifiers are frequently used for
* DOM element identifiers by the library, we avoid using dots to make for
* more sensible CSS selectors.
*
* TODO: Use a module pattern to avoid bloating the API with stuff like this.
*/
OpenLayers.Util.dotless = /\./g;
/**
* Function: modifyDOMElement
*
* Modifies many properties of a DOM element all at once. Passing in
* null to an individual parameter will avoid setting the attribute.
*
* Parameters:
* element - {DOMElement} DOM element to modify.
* id - {String} The element id attribute to set. Note that dots (".") will be
* replaced with underscore ("_") in setting the element id.
* px - {<OpenLayers.Pixel>|Object} The element left and top position,
* OpenLayers.Pixel or an object with
* a 'x' and 'y' properties.
* sz - {<OpenLayers.Size>|Object} The element width and height,
* OpenLayers.Size or an object with a
* 'w' and 'h' properties.
* position - {String} The position attribute. eg: absolute,
* relative, etc.
* border - {String} The style.border attribute. eg:
* solid black 2px
* overflow - {String} The style.overview attribute.
* opacity - {Float} Fractional value (0.0 - 1.0)
*/
OpenLayers.Util.modifyDOMElement = function(element, id, px, sz, position,
border, overflow, opacity) {
if (id) {
element.id = id.replace(OpenLayers.Util.dotless, "_");
}
if (px) {
element.style.left = px.x + "px";
element.style.top = px.y + "px";
}
if (sz) {
element.style.width = sz.w + "px";
element.style.height = sz.h + "px";
}
if (position) {
element.style.position = position;
}
if (border) {
element.style.border = border;
}
if (overflow) {
element.style.overflow = overflow;
}
if (parseFloat(opacity) >= 0.0 && parseFloat(opacity) < 1.0) {
element.style.filter = 'alpha(opacity=' + (opacity * 100) + ')';
element.style.opacity = opacity;
} else if (parseFloat(opacity) == 1.0) {
element.style.filter = '';
element.style.opacity = '';
}
};
/**
* Function: createDiv
* Creates a new div and optionally set some standard attributes.
* Null may be passed to each parameter if you do not wish to
* set a particular attribute.
* Note - zIndex is NOT set on the resulting div.
*
* Parameters:
* id - {String} An identifier for this element. If no id is
* passed an identifier will be created
* automatically. Note that dots (".") will be replaced with
* underscore ("_") when generating ids.
* px - {<OpenLayers.Pixel>|Object} The element left and top position,
* OpenLayers.Pixel or an object with
* a 'x' and 'y' properties.
* sz - {<OpenLayers.Size>|Object} The element width and height,
* OpenLayers.Size or an object with a
* 'w' and 'h' properties.
* imgURL - {String} A url pointing to an image to use as a
* background image.
* position - {String} The style.position value. eg: absolute,
* relative etc.
* border - {String} The the style.border value.
* eg: 2px solid black
* overflow - {String} The style.overflow value. Eg. hidden
* opacity - {Float} Fractional value (0.0 - 1.0)
*
* Returns:
* {DOMElement} A DOM Div created with the specified attributes.
*/
OpenLayers.Util.createDiv = function(id, px, sz, imgURL, position,
border, overflow, opacity) {
var dom = document.createElement('div');
if (imgURL) {
dom.style.backgroundImage = 'url(' + imgURL + ')';
}
//set generic properties
if (!id) {
id = OpenLayers.Util.createUniqueID("OpenLayersDiv");
}
if (!position) {
position = "absolute";
}
OpenLayers.Util.modifyDOMElement(dom, id, px, sz, position,
border, overflow, opacity);
return dom;
};
/**
* Function: createImage
* Creates an img element with specific attribute values.
*
* Parameters:
* id - {String} The id field for the img. If none assigned one will be
* automatically generated.
* px - {<OpenLayers.Pixel>|Object} The element left and top position,
* OpenLayers.Pixel or an object with
* a 'x' and 'y' properties.
* sz - {<OpenLayers.Size>|Object} The element width and height,
* OpenLayers.Size or an object with a
* 'w' and 'h' properties.
* imgURL - {String} The url to use as the image source.
* position - {String} The style.position value.
* border - {String} The border to place around the image.
* opacity - {Float} Fractional value (0.0 - 1.0)
* delayDisplay - {Boolean} If true waits until the image has been
* loaded.
*
* Returns:
* {DOMElement} A DOM Image created with the specified attributes.
*/
OpenLayers.Util.createImage = function(id, px, sz, imgURL, position, border,
opacity, delayDisplay) {
var image = document.createElement("img");
//set generic properties
if (!id) {
id = OpenLayers.Util.createUniqueID("OpenLayersDiv");
}
if (!position) {
position = "relative";
}
OpenLayers.Util.modifyDOMElement(image, id, px, sz, position,
border, null, opacity);
if (delayDisplay) {
image.style.display = "none";
function display() {
image.style.display = "";
OpenLayers.Event.stopObservingElement(image);
}
OpenLayers.Event.observe(image, "load", display);
OpenLayers.Event.observe(image, "error", display);
}
//set special properties
image.style.alt = id;
image.galleryImg = "no";
if (imgURL) {
image.src = imgURL;
}
return image;
};
/**
* Property: IMAGE_RELOAD_ATTEMPTS
* {Integer} How many times should we try to reload an image before giving up?
* Default is 0
*/
OpenLayers.IMAGE_RELOAD_ATTEMPTS = 0;
/**
* Property: alphaHackNeeded
* {Boolean} true if the png alpha hack is necessary and possible, false otherwise.
*/
OpenLayers.Util.alphaHackNeeded = null;
/**
* Function: alphaHack
* Checks whether it's necessary (and possible) to use the png alpha
* hack which allows alpha transparency for png images under Internet
* Explorer.
*
* Returns:
* {Boolean} true if the png alpha hack is necessary and possible, false otherwise.
*/
OpenLayers.Util.alphaHack = function() {
if (OpenLayers.Util.alphaHackNeeded == null) {
var arVersion = navigator.appVersion.split("MSIE");
var version = parseFloat(arVersion[1]);
var filter = false;
// IEs4Lin dies when trying to access document.body.filters, because
// the property is there, but requires a DLL that can't be provided. This
// means that we need to wrap this in a try/catch so that this can
// continue.
try {
filter = !!(document.body.filters);
} catch (e) {}
OpenLayers.Util.alphaHackNeeded = (filter &&
(version >= 5.5) && (version < 7));
}
return OpenLayers.Util.alphaHackNeeded;
};
/**
* Function: modifyAlphaImageDiv
*
* Parameters:
* div - {DOMElement} Div containing Alpha-adjusted Image
* id - {String}
* px - {<OpenLayers.Pixel>|Object} OpenLayers.Pixel or an object with
* a 'x' and 'y' properties.
* sz - {<OpenLayers.Size>|Object} OpenLayers.Size or an object with
* a 'w' and 'h' properties.
* imgURL - {String}
* position - {String}
* border - {String}
* sizing - {String} 'crop', 'scale', or 'image'. Default is "scale"
* opacity - {Float} Fractional value (0.0 - 1.0)
*/
OpenLayers.Util.modifyAlphaImageDiv = function(div, id, px, sz, imgURL,
position, border, sizing,
opacity) {
OpenLayers.Util.modifyDOMElement(div, id, px, sz, position,
null, null, opacity);
var img = div.childNodes[0];
if (imgURL) {
img.src = imgURL;
}
OpenLayers.Util.modifyDOMElement(img, div.id + "_innerImage", null, sz,
"relative", border);
if (OpenLayers.Util.alphaHack()) {
if(div.style.display != "none") {
div.style.display = "inline-block";
}
if (sizing == null) {
sizing = "scale";
}
div.style.filter = "progid:DXImageTransform.Microsoft" +
".AlphaImageLoader(src='" + img.src + "', " +
"sizingMethod='" + sizing + "')";
if (parseFloat(div.style.opacity) >= 0.0 &&
parseFloat(div.style.opacity) < 1.0) {
div.style.filter += " alpha(opacity=" + div.style.opacity * 100 + ")";
}
img.style.filter = "alpha(opacity=0)";
}
};
/**
* Function: createAlphaImageDiv
*
* Parameters:
* id - {String}
* px - {<OpenLayers.Pixel>|Object} OpenLayers.Pixel or an object with
* a 'x' and 'y' properties.
* sz - {<OpenLayers.Size>|Object} OpenLayers.Size or an object with
* a 'w' and 'h' properties.
* imgURL - {String}
* position - {String}
* border - {String}
* sizing - {String} 'crop', 'scale', or 'image'. Default is "scale"
* opacity - {Float} Fractional value (0.0 - 1.0)
* delayDisplay - {Boolean} If true waits until the image has been
* loaded.
*
* Returns:
* {DOMElement} A DOM Div created with a DOM Image inside it. If the hack is
* needed for transparency in IE, it is added.
*/
OpenLayers.Util.createAlphaImageDiv = function(id, px, sz, imgURL,
position, border, sizing,
opacity, delayDisplay) {
var div = OpenLayers.Util.createDiv();
var img = OpenLayers.Util.createImage(null, null, null, null, null, null,
null, delayDisplay);
img.className = "olAlphaImg";
div.appendChild(img);
OpenLayers.Util.modifyAlphaImageDiv(div, id, px, sz, imgURL, position,
border, sizing, opacity);
return div;
};
/**
* Function: upperCaseObject
* Creates a new hashtable and copies over all the keys from the
* passed-in object, but storing them under an uppercased
* version of the key at which they were stored.
*
* Parameters:
* object - {Object}
*
* Returns:
* {Object} A new Object with all the same keys but uppercased
*/
OpenLayers.Util.upperCaseObject = function (object) {
var uObject = {};
for (var key in object) {
uObject[key.toUpperCase()] = object[key];
}
return uObject;
};
/**
* Function: applyDefaults
* Takes an object and copies any properties that don't exist from
* another properties, by analogy with OpenLayers.Util.extend() from
* Prototype.js.
*
* Parameters:
* to - {Object} The destination object.
* from - {Object} The source object. Any properties of this object that
* are undefined in the to object will be set on the to object.
*
* Returns:
* {Object} A reference to the to object. Note that the to argument is modified
* in place and returned by this function.
*/
OpenLayers.Util.applyDefaults = function (to, from) {
to = to || {};
/*
* FF/Windows < 2.0.0.13 reports "Illegal operation on WrappedNative
* prototype object" when calling hawOwnProperty if the source object is an
* instance of window.Event.
*/
var fromIsEvt = typeof window.Event == "function"
&& from instanceof window.Event;
for (var key in from) {
if (to[key] === undefined ||
(!fromIsEvt && from.hasOwnProperty
&& from.hasOwnProperty(key) && !to.hasOwnProperty(key))) {
to[key] = from[key];
}
}
/**
* IE doesn't include the toString property when iterating over an object's
* properties with the for(property in object) syntax. Explicitly check if
* the source has its own toString property.
*/
if(!fromIsEvt && from && from.hasOwnProperty
&& from.hasOwnProperty('toString') && !to.hasOwnProperty('toString')) {
to.toString = from.toString;
}
return to;
};
/**
* Function: getParameterString
*
* Parameters:
* params - {Object}
*
* Returns:
* {String} A concatenation of the properties of an object in
* http parameter notation.
* (ex. <i>"key1=value1&key2=value2&key3=value3"</i>)
* If a parameter is actually a list, that parameter will then
* be set to a comma-separated list of values (foo,bar) instead
* of being URL escaped (foo%3Abar).
*/
OpenLayers.Util.getParameterString = function(params) {
var paramsArray = [];
for (var key in params) {
var value = params[key];
if ((value != null) && (typeof value != 'function')) {
var encodedValue;
if (typeof value == 'object' && value.constructor == Array) {
/* value is an array; encode items and separate with "," */
var encodedItemArray = [];
var item;
for (var itemIndex=0, len=value.length; itemIndex<len; itemIndex++) {
item = value[itemIndex];
encodedItemArray.push(encodeURIComponent(
(item === null || item === undefined) ? "" : item)
);
}
encodedValue = encodedItemArray.join(",");
}
else {
/* value is a string; simply encode */
encodedValue = encodeURIComponent(value);
}
paramsArray.push(encodeURIComponent(key) + "=" + encodedValue);
}
}
return paramsArray.join("&");
};
/**
* Function: urlAppend
* Appends a parameter string to a url. This function includes the logic for
* using the appropriate character (none, & or ?) to append to the url before
* appending the param string.
*
* Parameters:
* url - {String} The url to append to
* paramStr - {String} The param string to append
*
* Returns:
* {String} The new url
*/
OpenLayers.Util.urlAppend = function(url, paramStr) {
var newUrl = url;
if(paramStr) {
var parts = (url + " ").split(/[?&]/);
newUrl += (parts.pop() === " " ?
paramStr :
parts.length ? "&" + paramStr : "?" + paramStr);
}
return newUrl;
};
/**
* Function: getImagesLocation
*
* Returns:
* {String} The fully formatted image location string
*/
OpenLayers.Util.getImagesLocation = function() {
return OpenLayers.ImgPath || (OpenLayers._getScriptLocation() + "img/");
};
/**
* Function: getImageLocation
*
* Returns:
* {String} The fully formatted location string for a specified image
*/
OpenLayers.Util.getImageLocation = function(image) {
return OpenLayers.Util.getImagesLocation() + image;
};
/**
* Function: Try
* Execute functions until one of them doesn't throw an error.
* Capitalized because "try" is a reserved word in JavaScript.
* Taken directly from OpenLayers.Util.Try()
*
* Parameters:
* [*] - {Function} Any number of parameters may be passed to Try()
* It will attempt to execute each of them until one of them
* successfully executes.
* If none executes successfully, returns null.
*
* Returns:
* {*} The value returned by the first successfully executed function.
*/
OpenLayers.Util.Try = function() {
var returnValue = null;
for (var i=0, len=arguments.length; i<len; i++) {
var lambda = arguments[i];
try {
returnValue = lambda();
break;
} catch (e) {}
}
return returnValue;
};
/**
* Function: getXmlNodeValue
*
* Parameters:
* node - {XMLNode}
*
* Returns:
* {String} The text value of the given node, without breaking in firefox or IE
*/
OpenLayers.Util.getXmlNodeValue = function(node) {
var val = null;
OpenLayers.Util.Try(
function() {
val = node.text;
if (!val) {
val = node.textContent;
}
if (!val) {
val = node.firstChild.nodeValue;
}
},
function() {
val = node.textContent;
});
return val;
};
/**
* Function: mouseLeft
*
* Parameters:
* evt - {Event}
* div - {HTMLDivElement}
*
* Returns:
* {Boolean}
*/
OpenLayers.Util.mouseLeft = function (evt, div) {
// start with the element to which the mouse has moved
var target = (evt.relatedTarget) ? evt.relatedTarget : evt.toElement;
// walk up the DOM tree.
while (target != div && target != null) {
target = target.parentNode;
}
// if the target we stop at isn't the div, then we've left the div.
return (target != div);
};
/**
* Property: precision
* {Number} The number of significant digits to retain to avoid
* floating point precision errors.
*
* We use 14 as a "safe" default because, although IEEE 754 double floats
* (standard on most modern operating systems) support up to about 16
* significant digits, 14 significant digits are sufficient to represent
* sub-millimeter accuracy in any coordinate system that anyone is likely to
* use with OpenLayers.
*
* If DEFAULT_PRECISION is set to 0, the original non-truncating behavior
* of OpenLayers <2.8 is preserved. Be aware that this will cause problems
* with certain projections, e.g. spherical Mercator.
*
*/
OpenLayers.Util.DEFAULT_PRECISION = 14;
/**
* Function: toFloat
* Convenience method to cast an object to a Number, rounded to the
* desired floating point precision.
*
* Parameters:
* number - {Number} The number to cast and round.
* precision - {Number} An integer suitable for use with
* Number.toPrecision(). Defaults to OpenLayers.Util.DEFAULT_PRECISION.
* If set to 0, no rounding is performed.
*
* Returns:
* {Number} The cast, rounded number.
*/
OpenLayers.Util.toFloat = function (number, precision) {
if (precision == null) {
precision = OpenLayers.Util.DEFAULT_PRECISION;
}
if (typeof number !== "number") {
number = parseFloat(number);
}
return precision === 0 ? number :
parseFloat(number.toPrecision(precision));
};
/**
* Function: rad
*
* Parameters:
* x - {Float}
*
* Returns:
* {Float}
*/
OpenLayers.Util.rad = function(x) {return x*Math.PI/180;};
/**
* Function: deg
*
* Parameters:
* x - {Float}
*
* Returns:
* {Float}
*/
OpenLayers.Util.deg = function(x) {return x*180/Math.PI;};
/**
* Property: VincentyConstants
* {Object} Constants for Vincenty functions.
*/
OpenLayers.Util.VincentyConstants = {
a: 6378137,
b: 6356752.3142,
f: 1/298.257223563
};
/**
* APIFunction: distVincenty
* Given two objects representing points with geographic coordinates, this
* calculates the distance between those points on the surface of an
* ellipsoid.
*
* Parameters:
* p1 - {<OpenLayers.LonLat>} (or any object with both .lat, .lon properties)
* p2 - {<OpenLayers.LonLat>} (or any object with both .lat, .lon properties)
*
* Returns:
* {Float} The distance (in km) between the two input points as measured on an
* ellipsoid. Note that the input point objects must be in geographic
* coordinates (decimal degrees) and the return distance is in kilometers.
*/
OpenLayers.Util.distVincenty = function(p1, p2) {
var ct = OpenLayers.Util.VincentyConstants;
var a = ct.a, b = ct.b, f = ct.f;
var L = OpenLayers.Util.rad(p2.lon - p1.lon);
var U1 = Math.atan((1-f) * Math.tan(OpenLayers.Util.rad(p1.lat)));
var U2 = Math.atan((1-f) * Math.tan(OpenLayers.Util.rad(p2.lat)));
var sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
var sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);
var lambda = L, lambdaP = 2*Math.PI;
var iterLimit = 20;
while (Math.abs(lambda-lambdaP) > 1e-12 && --iterLimit>0) {
var sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda);
var sinSigma = Math.sqrt((cosU2*sinLambda) * (cosU2*sinLambda) +
(cosU1*sinU2-sinU1*cosU2*cosLambda) * (cosU1*sinU2-sinU1*cosU2*cosLambda));
if (sinSigma==0) {
return 0; // co-incident points
}
var cosSigma = sinU1*sinU2 + cosU1*cosU2*cosLambda;
var sigma = Math.atan2(sinSigma, cosSigma);
var alpha = Math.asin(cosU1 * cosU2 * sinLambda / sinSigma);
var cosSqAlpha = Math.cos(alpha) * Math.cos(alpha);
var cos2SigmaM = cosSigma - 2*sinU1*sinU2/cosSqAlpha;
var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
lambdaP = lambda;
lambda = L + (1-C) * f * Math.sin(alpha) *
(sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
}
if (iterLimit==0) {
return NaN; // formula failed to converge
}
var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
var s = b*A*(sigma-deltaSigma);
var d = s.toFixed(3)/1000; // round to 1mm precision
return d;
};
/**
* APIFunction: destinationVincenty
* Calculate destination point given start point lat/long (numeric degrees),
* bearing (numeric degrees) & distance (in m).
* Adapted from Chris Veness work, see
* http://www.movable-type.co.uk/scripts/latlong-vincenty-direct.html
*
* Parameters:
* lonlat - {<OpenLayers.LonLat>} (or any object with both .lat, .lon
* properties) The start point.
* brng - {Float} The bearing (degrees).
* dist - {Float} The ground distance (meters).
*
* Returns:
* {<OpenLayers.LonLat>} The destination point.
*/
OpenLayers.Util.destinationVincenty = function(lonlat, brng, dist) {
var u = OpenLayers.Util;
var ct = u.VincentyConstants;
var a = ct.a, b = ct.b, f = ct.f;
var lon1 = lonlat.lon;
var lat1 = lonlat.lat;
var s = dist;
var alpha1 = u.rad(brng);
var sinAlpha1 = Math.sin(alpha1);
var cosAlpha1 = Math.cos(alpha1);
var tanU1 = (1-f) * Math.tan(u.rad(lat1));
var cosU1 = 1 / Math.sqrt((1 + tanU1*tanU1)), sinU1 = tanU1*cosU1;
var sigma1 = Math.atan2(tanU1, cosAlpha1);
var sinAlpha = cosU1 * sinAlpha1;
var cosSqAlpha = 1 - sinAlpha*sinAlpha;
var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
var sigma = s / (b*A), sigmaP = 2*Math.PI;
while (Math.abs(sigma-sigmaP) > 1e-12) {
var cos2SigmaM = Math.cos(2*sigma1 + sigma);
var sinSigma = Math.sin(sigma);
var cosSigma = Math.cos(sigma);
var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
sigmaP = sigma;
sigma = s / (b*A) + deltaSigma;
}
var tmp = sinU1*sinSigma - cosU1*cosSigma*cosAlpha1;
var lat2 = Math.atan2(sinU1*cosSigma + cosU1*sinSigma*cosAlpha1,
(1-f)*Math.sqrt(sinAlpha*sinAlpha + tmp*tmp));
var lambda = Math.atan2(sinSigma*sinAlpha1, cosU1*cosSigma - sinU1*sinSigma*cosAlpha1);
var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
var L = lambda - (1-C) * f * sinAlpha *
(sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
var revAz = Math.atan2(sinAlpha, -tmp); // final bearing
return new OpenLayers.LonLat(lon1+u.deg(L), u.deg(lat2));
};
/**
* Function: getParameters
* Parse the parameters from a URL or from the current page itself into a
* JavaScript Object. Note that parameter values with commas are separated
* out into an Array.
*
* Parameters:
* url - {String} Optional url used to extract the query string.
* If url is null or is not supplied, query string is taken
* from the page location.
* options - {Object} Additional options. Optional.
*
* Valid options:
* splitArgs - {Boolean} Split comma delimited params into arrays? Default is
* true.
*
* Returns:
* {Object} An object of key/value pairs from the query string.
*/
OpenLayers.Util.getParameters = function(url, options) {
options = options || {};
// if no url specified, take it from the location bar
url = (url === null || url === undefined) ? window.location.href : url;
//parse out parameters portion of url string
var paramsString = "";
if (OpenLayers.String.contains(url, '?')) {
var start = url.indexOf('?') + 1;
var end = OpenLayers.String.contains(url, "#") ?
url.indexOf('#') : url.length;
paramsString = url.substring(start, end);
}
var parameters = {};
var pairs = paramsString.split(/[&;]/);
for(var i=0, len=pairs.length; i<len; ++i) {
var keyValue = pairs[i].split('=');
if (keyValue[0]) {
var key = keyValue[0];
try {
key = decodeURIComponent(key);
} catch (err) {
key = unescape(key);
}
// being liberal by replacing "+" with " "
var value = (keyValue[1] || '').replace(/\+/g, " ");
try {
value = decodeURIComponent(value);
} catch (err) {
value = unescape(value);
}
// follow OGC convention of comma delimited values
if (options.splitArgs !== false) {
value = value.split(",");
}
//if there's only one value, do not return as array
if (value.length == 1) {
value = value[0];
}
parameters[key] = value;
}
}
return parameters;
};
/**
* Property: lastSeqID
* {Integer} The ever-incrementing count variable.
* Used for generating unique ids.
*/
OpenLayers.Util.lastSeqID = 0;
/**
* Function: createUniqueID
* Create a unique identifier for this session. Each time this function
* is called, a counter is incremented. The return will be the optional
* prefix (defaults to "id_") appended with the counter value.
*
* Parameters:
* prefix - {String} Optional string to prefix unique id. Default is "id_".
* Note that dots (".") in the prefix will be replaced with underscore ("_").
*
* Returns:
* {String} A unique id string, built on the passed in prefix.
*/
OpenLayers.Util.createUniqueID = function(prefix) {
if (prefix == null) {
prefix = "id_";
} else {
prefix = prefix.replace(OpenLayers.Util.dotless, "_");
}
OpenLayers.Util.lastSeqID += 1;
return prefix + OpenLayers.Util.lastSeqID;
};
/**
* Constant: INCHES_PER_UNIT
* {Object} Constant inches per unit -- borrowed from MapServer mapscale.c
* derivation of nautical miles from http://en.wikipedia.org/wiki/Nautical_mile
* Includes the full set of units supported by CS-MAP (http://trac.osgeo.org/csmap/)
* and PROJ.4 (http://trac.osgeo.org/proj/)
* The hardcoded table is maintain in a CS-MAP source code module named CSdataU.c
* The hardcoded table of PROJ.4 units are in pj_units.c.
*/
OpenLayers.INCHES_PER_UNIT = {
'inches': 1.0,
'ft': 12.0,
'mi': 63360.0,
'm': 39.37,
'km': 39370,
'dd': 4374754,
'yd': 36
};
OpenLayers.INCHES_PER_UNIT["in"]= OpenLayers.INCHES_PER_UNIT.inches;
OpenLayers.INCHES_PER_UNIT["degrees"] = OpenLayers.INCHES_PER_UNIT.dd;
OpenLayers.INCHES_PER_UNIT["nmi"] = 1852 * OpenLayers.INCHES_PER_UNIT.m;
// Units from CS-Map
OpenLayers.METERS_PER_INCH = 0.02540005080010160020;
OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT, {
"Inch": OpenLayers.INCHES_PER_UNIT.inches,
"Meter": 1.0 / OpenLayers.METERS_PER_INCH, //EPSG:9001
"Foot": 0.30480060960121920243 / OpenLayers.METERS_PER_INCH, //EPSG:9003
"IFoot": 0.30480000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9002
"ClarkeFoot": 0.3047972651151 / OpenLayers.METERS_PER_INCH, //EPSG:9005
"SearsFoot": 0.30479947153867624624 / OpenLayers.METERS_PER_INCH, //EPSG:9041
"GoldCoastFoot": 0.30479971018150881758 / OpenLayers.METERS_PER_INCH, //EPSG:9094
"IInch": 0.02540000000000000000 / OpenLayers.METERS_PER_INCH,
"MicroInch": 0.00002540000000000000 / OpenLayers.METERS_PER_INCH,
"Mil": 0.00000002540000000000 / OpenLayers.METERS_PER_INCH,
"Centimeter": 0.01000000000000000000 / OpenLayers.METERS_PER_INCH,
"Kilometer": 1000.00000000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9036
"Yard": 0.91440182880365760731 / OpenLayers.METERS_PER_INCH,
"SearsYard": 0.914398414616029 / OpenLayers.METERS_PER_INCH, //EPSG:9040
"IndianYard": 0.91439853074444079983 / OpenLayers.METERS_PER_INCH, //EPSG:9084
"IndianYd37": 0.91439523 / OpenLayers.METERS_PER_INCH, //EPSG:9085
"IndianYd62": 0.9143988 / OpenLayers.METERS_PER_INCH, //EPSG:9086
"IndianYd75": 0.9143985 / OpenLayers.METERS_PER_INCH, //EPSG:9087
"IndianFoot": 0.30479951 / OpenLayers.METERS_PER_INCH, //EPSG:9080
"IndianFt37": 0.30479841 / OpenLayers.METERS_PER_INCH, //EPSG:9081
"IndianFt62": 0.3047996 / OpenLayers.METERS_PER_INCH, //EPSG:9082
"IndianFt75": 0.3047995 / OpenLayers.METERS_PER_INCH, //EPSG:9083
"Mile": 1609.34721869443738887477 / OpenLayers.METERS_PER_INCH,
"IYard": 0.91440000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9096
"IMile": 1609.34400000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9093
"NautM": 1852.00000000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9030
"Lat-66": 110943.316488932731 / OpenLayers.METERS_PER_INCH,
"Lat-83": 110946.25736872234125 / OpenLayers.METERS_PER_INCH,
"Decimeter": 0.10000000000000000000 / OpenLayers.METERS_PER_INCH,
"Millimeter": 0.00100000000000000000 / OpenLayers.METERS_PER_INCH,
"Dekameter": 10.00000000000000000000 / OpenLayers.METERS_PER_INCH,
"Decameter": 10.00000000000000000000 / OpenLayers.METERS_PER_INCH,
"Hectometer": 100.00000000000000000000 / OpenLayers.METERS_PER_INCH,
"GermanMeter": 1.0000135965 / OpenLayers.METERS_PER_INCH, //EPSG:9031
"CaGrid": 0.999738 / OpenLayers.METERS_PER_INCH,
"ClarkeChain": 20.1166194976 / OpenLayers.METERS_PER_INCH, //EPSG:9038
"GunterChain": 20.11684023368047 / OpenLayers.METERS_PER_INCH, //EPSG:9033
"BenoitChain": 20.116782494375872 / OpenLayers.METERS_PER_INCH, //EPSG:9062
"SearsChain": 20.11676512155 / OpenLayers.METERS_PER_INCH, //EPSG:9042
"ClarkeLink": 0.201166194976 / OpenLayers.METERS_PER_INCH, //EPSG:9039
"GunterLink": 0.2011684023368047 / OpenLayers.METERS_PER_INCH, //EPSG:9034
"BenoitLink": 0.20116782494375872 / OpenLayers.METERS_PER_INCH, //EPSG:9063
"SearsLink": 0.2011676512155 / OpenLayers.METERS_PER_INCH, //EPSG:9043
"Rod": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
"IntnlChain": 20.1168 / OpenLayers.METERS_PER_INCH, //EPSG:9097
"IntnlLink": 0.201168 / OpenLayers.METERS_PER_INCH, //EPSG:9098
"Perch": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
"Pole": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
"Furlong": 201.1684023368046 / OpenLayers.METERS_PER_INCH,
"Rood": 3.778266898 / OpenLayers.METERS_PER_INCH,
"CapeFoot": 0.3047972615 / OpenLayers.METERS_PER_INCH,
"Brealey": 375.00000000000000000000 / OpenLayers.METERS_PER_INCH,
"ModAmFt": 0.304812252984505969011938 / OpenLayers.METERS_PER_INCH,
"Fathom": 1.8288 / OpenLayers.METERS_PER_INCH,
"NautM-UK": 1853.184 / OpenLayers.METERS_PER_INCH,
"50kilometers": 50000.0 / OpenLayers.METERS_PER_INCH,
"150kilometers": 150000.0 / OpenLayers.METERS_PER_INCH
});
//unit abbreviations supported by PROJ.4
OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT, {
"mm": OpenLayers.INCHES_PER_UNIT["Meter"] / 1000.0,
"cm": OpenLayers.INCHES_PER_UNIT["Meter"] / 100.0,
"dm": OpenLayers.INCHES_PER_UNIT["Meter"] * 100.0,
"km": OpenLayers.INCHES_PER_UNIT["Meter"] * 1000.0,
"kmi": OpenLayers.INCHES_PER_UNIT["nmi"], //International Nautical Mile
"fath": OpenLayers.INCHES_PER_UNIT["Fathom"], //International Fathom
"ch": OpenLayers.INCHES_PER_UNIT["IntnlChain"], //International Chain
"link": OpenLayers.INCHES_PER_UNIT["IntnlLink"], //International Link
"us-in": OpenLayers.INCHES_PER_UNIT["inches"], //U.S. Surveyor's Inch
"us-ft": OpenLayers.INCHES_PER_UNIT["Foot"], //U.S. Surveyor's Foot
"us-yd": OpenLayers.INCHES_PER_UNIT["Yard"], //U.S. Surveyor's Yard
"us-ch": OpenLayers.INCHES_PER_UNIT["GunterChain"], //U.S. Surveyor's Chain
"us-mi": OpenLayers.INCHES_PER_UNIT["Mile"], //U.S. Surveyor's Statute Mile
"ind-yd": OpenLayers.INCHES_PER_UNIT["IndianYd37"], //Indian Yard
"ind-ft": OpenLayers.INCHES_PER_UNIT["IndianFt37"], //Indian Foot
"ind-ch": 20.11669506 / OpenLayers.METERS_PER_INCH //Indian Chain
});
/**
* Constant: DOTS_PER_INCH
* {Integer} 72 (A sensible default)
*/
OpenLayers.DOTS_PER_INCH = 72;
/**
* Function: normalizeScale
*
* Parameters:
* scale - {float}
*
* Returns:
* {Float} A normalized scale value, in 1 / X format.
* This means that if a value less than one ( already 1/x) is passed
* in, it just returns scale directly. Otherwise, it returns
* 1 / scale
*/
OpenLayers.Util.normalizeScale = function (scale) {
var normScale = (scale > 1.0) ? (1.0 / scale)
: scale;
return normScale;
};
/**
* Function: getResolutionFromScale
*
* Parameters:
* scale - {Float}
* units - {String} Index into OpenLayers.INCHES_PER_UNIT hashtable.
* Default is degrees
*
* Returns:
* {Float} The corresponding resolution given passed-in scale and unit
* parameters. If the given scale is falsey, the returned resolution will
* be undefined.
*/
OpenLayers.Util.getResolutionFromScale = function (scale, units) {
var resolution;
if (scale) {
if (units == null) {
units = "degrees";
}
var normScale = OpenLayers.Util.normalizeScale(scale);
resolution = 1 / (normScale * OpenLayers.INCHES_PER_UNIT[units]
* OpenLayers.DOTS_PER_INCH);
}
return resolution;
};
/**
* Function: getScaleFromResolution
*
* Parameters:
* resolution - {Float}
* units - {String} Index into OpenLayers.INCHES_PER_UNIT hashtable.
* Default is degrees
*
* Returns:
* {Float} The corresponding scale given passed-in resolution and unit
* parameters.
*/
OpenLayers.Util.getScaleFromResolution = function (resolution, units) {
if (units == null) {
units = "degrees";
}
var scale = resolution * OpenLayers.INCHES_PER_UNIT[units] *
OpenLayers.DOTS_PER_INCH;
return scale;
};
/**
* Function: pagePosition
* Calculates the position of an element on the page (see
* http://code.google.com/p/doctype/wiki/ArticlePageOffset)
*
* OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is
* Copyright (c) 2006, Yahoo! Inc.
* All rights reserved.
*
* Redistribution and use of this software in source and binary forms, with or
* without modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of Yahoo! Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission of Yahoo! Inc.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* Parameters:
* forElement - {DOMElement}
*
* Returns:
* {Array} two item array, Left value then Top value.
*/
OpenLayers.Util.pagePosition = function(forElement) {
// NOTE: If element is hidden (display none or disconnected or any the
// ancestors are hidden) we get (0,0) by default but we still do the
// accumulation of scroll position.
var pos = [0, 0];
var viewportElement = OpenLayers.Util.getViewportElement();
if (!forElement || forElement == window || forElement == viewportElement) {
// viewport is always at 0,0 as that defined the coordinate system for
// this function - this avoids special case checks in the code below
return pos;
}
// Gecko browsers normally use getBoxObjectFor to calculate the position.
// When invoked for an element with an implicit absolute position though it
// can be off by one. Therefore the recursive implementation is used in
// those (relatively rare) cases.
var BUGGY_GECKO_BOX_OBJECT =
OpenLayers.IS_GECKO && document.getBoxObjectFor &&
OpenLayers.Element.getStyle(forElement, 'position') == 'absolute' &&
(forElement.style.top == '' || forElement.style.left == '');
var parent = null;
var box;
if (forElement.getBoundingClientRect) { // IE
box = forElement.getBoundingClientRect();
var scrollTop = window.pageYOffset || viewportElement.scrollTop;
var scrollLeft = window.pageXOffset || viewportElement.scrollLeft;
pos[0] = box.left + scrollLeft;
pos[1] = box.top + scrollTop;
} else if (document.getBoxObjectFor && !BUGGY_GECKO_BOX_OBJECT) { // gecko
// Gecko ignores the scroll values for ancestors, up to 1.9. See:
// https://bugzilla.mozilla.org/show_bug.cgi?id=328881 and
// https://bugzilla.mozilla.org/show_bug.cgi?id=330619
box = document.getBoxObjectFor(forElement);
var vpBox = document.getBoxObjectFor(viewportElement);
pos[0] = box.screenX - vpBox.screenX;
pos[1] = box.screenY - vpBox.screenY;
} else { // safari/opera
pos[0] = forElement.offsetLeft;
pos[1] = forElement.offsetTop;
parent = forElement.offsetParent;
if (parent != forElement) {
while (parent) {
pos[0] += parent.offsetLeft;
pos[1] += parent.offsetTop;
parent = parent.offsetParent;
}
}
var browser = OpenLayers.BROWSER_NAME;
// opera & (safari absolute) incorrectly account for body offsetTop
if (browser == "opera" || (browser == "safari" &&
OpenLayers.Element.getStyle(forElement, 'position') == 'absolute')) {
pos[1] -= document.body.offsetTop;
}
// accumulate the scroll positions for everything but the body element
parent = forElement.offsetParent;
while (parent && parent != document.body) {
pos[0] -= parent.scrollLeft;
// see https://bugs.opera.com/show_bug.cgi?id=249965
if (browser != "opera" || parent.tagName != 'TR') {
pos[1] -= parent.scrollTop;
}
parent = parent.offsetParent;
}
}
return pos;
};
/**
* Function: getViewportElement
* Returns die viewport element of the document. The viewport element is
* usually document.documentElement, except in IE,where it is either
* document.body or document.documentElement, depending on the document's
* compatibility mode (see
* http://code.google.com/p/doctype/wiki/ArticleClientViewportElement)
*
* Returns:
* {DOMElement}
*/
OpenLayers.Util.getViewportElement = function() {
var viewportElement = arguments.callee.viewportElement;
if (viewportElement == undefined) {
viewportElement = (OpenLayers.BROWSER_NAME == "msie" &&
document.compatMode != 'CSS1Compat') ? document.body :
document.documentElement;
arguments.callee.viewportElement = viewportElement;
}
return viewportElement;
};
/**
* Function: isEquivalentUrl
* Test two URLs for equivalence.
*
* Setting 'ignoreCase' allows for case-independent comparison.
*
* Comparison is based on:
* - Protocol
* - Host (evaluated without the port)
* - Port (set 'ignorePort80' to ignore "80" values)
* - Hash ( set 'ignoreHash' to disable)
* - Pathname (for relative <-> absolute comparison)
* - Arguments (so they can be out of order)
*
* Parameters:
* url1 - {String}
* url2 - {String}
* options - {Object} Allows for customization of comparison:
* 'ignoreCase' - Default is True
* 'ignorePort80' - Default is True
* 'ignoreHash' - Default is True
*
* Returns:
* {Boolean} Whether or not the two URLs are equivalent
*/
OpenLayers.Util.isEquivalentUrl = function(url1, url2, options) {
options = options || {};
OpenLayers.Util.applyDefaults(options, {
ignoreCase: true,
ignorePort80: true,
ignoreHash: true,
splitArgs: false
});
var urlObj1 = OpenLayers.Util.createUrlObject(url1, options);
var urlObj2 = OpenLayers.Util.createUrlObject(url2, options);
//compare all keys except for "args" (treated below)
for(var key in urlObj1) {
if(key !== "args") {
if(urlObj1[key] != urlObj2[key]) {
return false;
}
}
}
// compare search args - irrespective of order
for(var key in urlObj1.args) {
if(urlObj1.args[key] != urlObj2.args[key]) {
return false;
}
delete urlObj2.args[key];
}
// urlObj2 shouldn't have any args left
for(var key in urlObj2.args) {
return false;
}
return true;
};
/**
* Function: createUrlObject
*
* Parameters:
* url - {String}
* options - {Object} A hash of options.
*
* Valid options:
* ignoreCase - {Boolean} lowercase url,
* ignorePort80 - {Boolean} don't include explicit port if port is 80,
* ignoreHash - {Boolean} Don't include part of url after the hash (#).
* splitArgs - {Boolean} Split comma delimited params into arrays? Default is
* true.
*
* Returns:
* {Object} An object with separate url, a, port, host, and args parsed out
* and ready for comparison
*/
OpenLayers.Util.createUrlObject = function(url, options) {
options = options || {};
// deal with relative urls first
if(!(/^\w+:\/\//).test(url)) {
var loc = window.location;
var port = loc.port ? ":" + loc.port : "";
var fullUrl = loc.protocol + "//" + loc.host.split(":").shift() + port;
if(url.indexOf("/") === 0) {
// full pathname
url = fullUrl + url;
} else {
// relative to current path
var parts = loc.pathname.split("/");
parts.pop();
url = fullUrl + parts.join("/") + "/" + url;
}
}
if (options.ignoreCase) {
url = url.toLowerCase();
}
var a = document.createElement('a');
a.href = url;
var urlObject = {};
//host (without port)
urlObject.host = a.host.split(":").shift();
//protocol
urlObject.protocol = a.protocol;
//port (get uniform browser behavior with port 80 here)
if(options.ignorePort80) {
urlObject.port = (a.port == "80" || a.port == "0") ? "" : a.port;
} else {
urlObject.port = (a.port == "" || a.port == "0") ? "80" : a.port;
}
//hash
urlObject.hash = (options.ignoreHash || a.hash === "#") ? "" : a.hash;
//args
var queryString = a.search;
if (!queryString) {
var qMark = url.indexOf("?");
queryString = (qMark != -1) ? url.substr(qMark) : "";
}
urlObject.args = OpenLayers.Util.getParameters(queryString,
{splitArgs: options.splitArgs});
// pathname
//
// This is a workaround for Internet Explorer where
// window.location.pathname has a leading "/", but
// a.pathname has no leading "/".
urlObject.pathname = (a.pathname.charAt(0) == "/") ? a.pathname : "/" + a.pathname;
return urlObject;
};
/**
* Function: removeTail
* Takes a url and removes everything after the ? and #
*
* Parameters:
* url - {String} The url to process
*
* Returns:
* {String} The string with all queryString and Hash removed
*/
OpenLayers.Util.removeTail = function(url) {
var head = null;
var qMark = url.indexOf("?");
var hashMark = url.indexOf("#");
if (qMark == -1) {
head = (hashMark != -1) ? url.substr(0,hashMark) : url;
} else {
head = (hashMark != -1) ? url.substr(0,Math.min(qMark, hashMark))
: url.substr(0, qMark);
}
return head;
};
/**
* Constant: IS_GECKO
* {Boolean} True if the userAgent reports the browser to use the Gecko engine
*/
OpenLayers.IS_GECKO = (function() {
var ua = navigator.userAgent.toLowerCase();
return ua.indexOf("webkit") == -1 && ua.indexOf("gecko") != -1;
})();
/**
* Constant: CANVAS_SUPPORTED
* {Boolean} True if canvas 2d is supported.
*/
OpenLayers.CANVAS_SUPPORTED = (function() {
var elem = document.createElement('canvas');
return !!(elem.getContext && elem.getContext('2d'));
})();
/**
* Constant: BROWSER_NAME
* {String}
* A substring of the navigator.userAgent property. Depending on the userAgent
* property, this will be the empty string or one of the following:
* * "opera" -- Opera
* * "msie" -- Internet Explorer
* * "safari" -- Safari
* * "firefox" -- Firefox
* * "mozilla" -- Mozilla
*/
OpenLayers.BROWSER_NAME = (function() {
var name = "";
var ua = navigator.userAgent.toLowerCase();
if (ua.indexOf("opera") != -1) {
name = "opera";
} else if (ua.indexOf("msie") != -1) {
name = "msie";
} else if (ua.indexOf("safari") != -1) {
name = "safari";
} else if (ua.indexOf("mozilla") != -1) {
if (ua.indexOf("firefox") != -1) {
name = "firefox";
} else {
name = "mozilla";
}
}
return name;
})();
/**
* Function: getBrowserName
*
* Returns:
* {String} A string which specifies which is the current
* browser in which we are running.
*
* Currently-supported browser detection and codes:
* * 'opera' -- Opera
* * 'msie' -- Internet Explorer
* * 'safari' -- Safari
* * 'firefox' -- Firefox
* * 'mozilla' -- Mozilla
*
* If we are unable to property identify the browser, we
* return an empty string.
*/
OpenLayers.Util.getBrowserName = function() {
return OpenLayers.BROWSER_NAME;
};
/**
* Method: getRenderedDimensions
* Renders the contentHTML offscreen to determine actual dimensions for
* popup sizing. As we need layout to determine dimensions the content
* is rendered -9999px to the left and absolute to ensure the
* scrollbars do not flicker
*
* Parameters:
* contentHTML
* size - {<OpenLayers.Size>} If either the 'w' or 'h' properties is
* specified, we fix that dimension of the div to be measured. This is
* useful in the case where we have a limit in one dimension and must
* therefore meaure the flow in the other dimension.
* options - {Object}
*
* Allowed Options:
* displayClass - {String} Optional parameter. A CSS class name(s) string
* to provide the CSS context of the rendered content.
* containerElement - {DOMElement} Optional parameter. Insert the HTML to
* this node instead of the body root when calculating dimensions.
*
* Returns:
* {<OpenLayers.Size>}
*/
OpenLayers.Util.getRenderedDimensions = function(contentHTML, size, options) {
var w, h;
// create temp container div with restricted size
var container = document.createElement("div");
container.style.visibility = "hidden";
var containerElement = (options && options.containerElement)
? options.containerElement : document.body;
// Opera and IE7 can't handle a node with position:aboslute if it inherits
// position:absolute from a parent.
var parentHasPositionAbsolute = false;
var superContainer = null;
var parent = containerElement;
while (parent && parent.tagName.toLowerCase()!="body") {
var parentPosition = OpenLayers.Element.getStyle(parent, "position");
if(parentPosition == "absolute") {
parentHasPositionAbsolute = true;
break;
} else if (parentPosition && parentPosition != "static") {
break;
}
parent = parent.parentNode;
}
if(parentHasPositionAbsolute && (containerElement.clientHeight === 0 ||
containerElement.clientWidth === 0) ){
superContainer = document.createElement("div");
superContainer.style.visibility = "hidden";
superContainer.style.position = "absolute";
superContainer.style.overflow = "visible";
superContainer.style.width = document.body.clientWidth + "px";
superContainer.style.height = document.body.clientHeight + "px";
superContainer.appendChild(container);
}
container.style.position = "absolute";
//fix a dimension, if specified.
if (size) {
if (size.w) {
w = size.w;
container.style.width = w + "px";
} else if (size.h) {
h = size.h;
container.style.height = h + "px";
}
}
//add css classes, if specified
if (options && options.displayClass) {
container.className = options.displayClass;
}
// create temp content div and assign content
var content = document.createElement("div");
content.innerHTML = contentHTML;
// we need overflow visible when calculating the size
content.style.overflow = "visible";
if (content.childNodes) {
for (var i=0, l=content.childNodes.length; i<l; i++) {
if (!content.childNodes[i].style) continue;
content.childNodes[i].style.overflow = "visible";
}
}
// add content to restricted container
container.appendChild(content);
// append container to body for rendering
if (superContainer) {
containerElement.appendChild(superContainer);
} else {
containerElement.appendChild(container);
}
// calculate scroll width of content and add corners and shadow width
if (!w) {
w = parseInt(content.scrollWidth);
// update container width to allow height to adjust
container.style.width = w + "px";
}
// capture height and add shadow and corner image widths
if (!h) {
h = parseInt(content.scrollHeight);
}
// remove elements
container.removeChild(content);
if (superContainer) {
superContainer.removeChild(container);
containerElement.removeChild(superContainer);
} else {
containerElement.removeChild(container);
}
return new OpenLayers.Size(w, h);
};
/**
* APIFunction: getScrollbarWidth
* This function has been modified by the OpenLayers from the original version,
* written by Matthew Eernisse and released under the Apache 2
* license here:
*
* http://www.fleegix.org/articles/2006/05/30/getting-the-scrollbar-width-in-pixels
*
* It has been modified simply to cache its value, since it is physically
* impossible that this code could ever run in more than one browser at
* once.
*
* Returns:
* {Integer}
*/
OpenLayers.Util.getScrollbarWidth = function() {
var scrollbarWidth = OpenLayers.Util._scrollbarWidth;
if (scrollbarWidth == null) {
var scr = null;
var inn = null;
var wNoScroll = 0;
var wScroll = 0;
// Outer scrolling div
scr = document.createElement('div');
scr.style.position = 'absolute';
scr.style.top = '-1000px';
scr.style.left = '-1000px';
scr.style.width = '100px';
scr.style.height = '50px';
// Start with no scrollbar
scr.style.overflow = 'hidden';
// Inner content div
inn = document.createElement('div');
inn.style.width = '100%';
inn.style.height = '200px';
// Put the inner div in the scrolling div
scr.appendChild(inn);
// Append the scrolling div to the doc
document.body.appendChild(scr);
// Width of the inner div sans scrollbar
wNoScroll = inn.offsetWidth;
// Add the scrollbar
scr.style.overflow = 'scroll';
// Width of the inner div width scrollbar
wScroll = inn.offsetWidth;
// Remove the scrolling div from the doc
document.body.removeChild(document.body.lastChild);
// Pixel width of the scroller
OpenLayers.Util._scrollbarWidth = (wNoScroll - wScroll);
scrollbarWidth = OpenLayers.Util._scrollbarWidth;
}
return scrollbarWidth;
};
/**
* APIFunction: getFormattedLonLat
* This function will return latitude or longitude value formatted as
*
* Parameters:
* coordinate - {Float} the coordinate value to be formatted
* axis - {String} value of either 'lat' or 'lon' to indicate which axis is to
* to be formatted (default = lat)
* dmsOption - {String} specify the precision of the output can be one of:
* 'dms' show degrees minutes and seconds
* 'dm' show only degrees and minutes
* 'd' show only degrees
*
* Returns:
* {String} the coordinate value formatted as a string
*/
OpenLayers.Util.getFormattedLonLat = function(coordinate, axis, dmsOption) {
if (!dmsOption) {
dmsOption = 'dms'; //default to show degree, minutes, seconds
}
coordinate = (coordinate+540)%360 - 180; // normalize for sphere being round
var abscoordinate = Math.abs(coordinate);
var coordinatedegrees = Math.floor(abscoordinate);
var coordinateminutes = (abscoordinate - coordinatedegrees)/(1/60);
var tempcoordinateminutes = coordinateminutes;
coordinateminutes = Math.floor(coordinateminutes);
var coordinateseconds = (tempcoordinateminutes - coordinateminutes)/(1/60);
coordinateseconds = Math.round(coordinateseconds*10);
coordinateseconds /= 10;
if( coordinateseconds >= 60) {
coordinateseconds -= 60;
coordinateminutes += 1;
if( coordinateminutes >= 60) {
coordinateminutes -= 60;
coordinatedegrees += 1;
}
}
if( coordinatedegrees < 10 ) {
coordinatedegrees = "0" + coordinatedegrees;
}
var str = coordinatedegrees + "\u00B0";
if (dmsOption.indexOf('dm') >= 0) {
if( coordinateminutes < 10 ) {
coordinateminutes = "0" + coordinateminutes;
}
str += coordinateminutes + "'";
if (dmsOption.indexOf('dms') >= 0) {
if( coordinateseconds < 10 ) {
coordinateseconds = "0" + coordinateseconds;
}
str += coordinateseconds + '"';
}
}
if (axis == "lon") {
str += coordinate < 0 ? OpenLayers.i18n("W") : OpenLayers.i18n("E");
} else {
str += coordinate < 0 ? OpenLayers.i18n("S") : OpenLayers.i18n("N");
}
return str;
};
/**
* Function: getConstructor
* Take an OpenLayers style CLASS_NAME and return a constructor.
*
* Parameters:
* className - {String} The dot delimited class name (e.g. 'OpenLayers.Foo').
*
* Returns:
* {Function} The constructor.
*/
OpenLayers.Util.getConstructor = function(className) {
var Constructor;
var parts = className.split('.');
if (parts[0] === "OpenLayers") {
Constructor = OpenLayers;
} else {
// someone extended our base class and used their own namespace
// this will not work when the library is evaluated in a closure
// but it is the best we can do (until we ourselves provide a global)
Constructor = window[parts[0]];
}
for (var i = 1, ii = parts.length; i < ii; ++i) {
Constructor = Constructor[parts[i]];
}
return Constructor;
};
/* ======================================================================
OpenLayers/Feature.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/BaseTypes/Class.js
* @requires OpenLayers/Util.js
*/
/**
* Class: OpenLayers.Feature
* Features are combinations of geography and attributes. The OpenLayers.Feature
* class specifically combines a marker and a lonlat.
*/
OpenLayers.Feature = OpenLayers.Class({
/**
* Property: layer
* {<OpenLayers.Layer>}
*/
layer: null,
/**
* Property: id
* {String}
*/
id: null,
/**
* Property: lonlat
* {<OpenLayers.LonLat>}
*/
lonlat: null,
/**
* Property: data
* {Object}
*/
data: null,
/**
* Property: marker
* {<OpenLayers.Marker>}
*/
marker: null,
/**
* APIProperty: popupClass
* {<OpenLayers.Class>} The class which will be used to instantiate
* a new Popup. Default is <OpenLayers.Popup.Anchored>.
*/
popupClass: null,
/**
* Property: popup
* {<OpenLayers.Popup>}
*/
popup: null,
/**
* Constructor: OpenLayers.Feature
* Constructor for features.
*
* Parameters:
* layer - {<OpenLayers.Layer>}
* lonlat - {<OpenLayers.LonLat>}
* data - {Object}
*
* Returns:
* {<OpenLayers.Feature>}
*/
initialize: function(layer, lonlat, data) {
this.layer = layer;
this.lonlat = lonlat;
this.data = (data != null) ? data : {};
this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
},
/**
* Method: destroy
* nullify references to prevent circular references and memory leaks
*/
destroy: function() {
//remove the popup from the map
if ((this.layer != null) && (this.layer.map != null)) {
if (this.popup != null) {
this.layer.map.removePopup(this.popup);
}
}
// remove the marker from the layer
if (this.layer != null && this.marker != null) {
this.layer.removeMarker(this.marker);
}
this.layer = null;
this.id = null;
this.lonlat = null;
this.data = null;
if (this.marker != null) {
this.destroyMarker(this.marker);
this.marker = null;
}
if (this.popup != null) {
this.destroyPopup(this.popup);
this.popup = null;
}
},
/**
* Method: onScreen
*
* Returns:
* {Boolean} Whether or not the feature is currently visible on screen
* (based on its 'lonlat' property)
*/
onScreen:function() {
var onScreen = false;
if ((this.layer != null) && (this.layer.map != null)) {
var screenBounds = this.layer.map.getExtent();
onScreen = screenBounds.containsLonLat(this.lonlat);
}
return onScreen;
},
/**
* Method: createMarker
* Based on the data associated with the Feature, create and return a marker object.
*
* Returns:
* {<OpenLayers.Marker>} A Marker Object created from the 'lonlat' and 'icon' properties
* set in this.data. If no 'lonlat' is set, returns null. If no
* 'icon' is set, OpenLayers.Marker() will load the default image.
*
* Note - this.marker is set to return value
*
*/
createMarker: function() {
if (this.lonlat != null) {
this.marker = new OpenLayers.Marker(this.lonlat, this.data.icon);
}
return this.marker;
},
/**
* Method: destroyMarker
* Destroys marker.
* If user overrides the createMarker() function, s/he should be able
* to also specify an alternative function for destroying it
*/
destroyMarker: function() {
this.marker.destroy();
},
/**
* Method: createPopup
* Creates a popup object created from the 'lonlat', 'popupSize',
* and 'popupContentHTML' properties set in this.data. It uses
* this.marker.icon as default anchor.
*
* If no 'lonlat' is set, returns null.
* If no this.marker has been created, no anchor is sent.
*
* Note - the returned popup object is 'owned' by the feature, so you
* cannot use the popup's destroy method to discard the popup.
* Instead, you must use the feature's destroyPopup
*
* Note - this.popup is set to return value
*
* Parameters:
* closeBox - {Boolean} create popup with closebox or not
*
* Returns:
* {<OpenLayers.Popup>} Returns the created popup, which is also set
* as 'popup' property of this feature. Will be of whatever type
* specified by this feature's 'popupClass' property, but must be
* of type <OpenLayers.Popup>.
*
*/
createPopup: function(closeBox) {
if (this.lonlat != null) {
if (!this.popup) {
var anchor = (this.marker) ? this.marker.icon : null;
var popupClass = this.popupClass ?
this.popupClass : OpenLayers.Popup.Anchored;
this.popup = new popupClass(this.id + "_popup",
this.lonlat,
this.data.popupSize,
this.data.popupContentHTML,
anchor,
closeBox);
}
if (this.data.overflow != null) {
this.popup.contentDiv.style.overflow = this.data.overflow;
}
this.popup.feature = this;
}
return this.popup;
},
/**
* Method: destroyPopup
* Destroys the popup created via createPopup.
*
* As with the marker, if user overrides the createPopup() function, s/he
* should also be able to override the destruction
*/
destroyPopup: function() {
if (this.popup) {
this.popup.feature = null;
this.popup.destroy();
this.popup = null;
}
},
CLASS_NAME: "OpenLayers.Feature"
});
/* ======================================================================
OpenLayers/Feature/Vector.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
// TRASH THIS
OpenLayers.State = {
/** states */
UNKNOWN: 'Unknown',
INSERT: 'Insert',
UPDATE: 'Update',
DELETE: 'Delete'
};
/**
* @requires OpenLayers/Feature.js
* @requires OpenLayers/Util.js
*/
/**
* Class: OpenLayers.Feature.Vector
* Vector features use the OpenLayers.Geometry classes as geometry description.
* They have an 'attributes' property, which is the data object, and a 'style'
* property, the default values of which are defined in the
* <OpenLayers.Feature.Vector.style> objects.
*
* Inherits from:
* - <OpenLayers.Feature>
*/
OpenLayers.Feature.Vector = OpenLayers.Class(OpenLayers.Feature, {
/**
* Property: fid
* {String}
*/
fid: null,
/**
* APIProperty: geometry
* {<OpenLayers.Geometry>}
*/
geometry: null,
/**
* APIProperty: attributes
* {Object} This object holds arbitrary, serializable properties that
* describe the feature.
*/
attributes: null,
/**
* Property: bounds
* {<OpenLayers.Bounds>} The box bounding that feature's geometry, that
* property can be set by an <OpenLayers.Format> object when
* deserializing the feature, so in most cases it represents an
* information set by the server.
*/
bounds: null,
/**
* Property: state
* {String}
*/
state: null,
/**
* APIProperty: style
* {Object}
*/
style: null,
/**
* APIProperty: url
* {String} If this property is set it will be taken into account by
* {<OpenLayers.HTTP>} when updating or deleting the feature.
*/
url: null,
/**
* Property: renderIntent
* {String} rendering intent currently being used
*/
renderIntent: "default",
/**
* APIProperty: modified
* {Object} An object with the originals of the geometry and attributes of
* the feature, if they were changed. Currently this property is only read
* by <OpenLayers.Format.WFST.v1>, and written by
* <OpenLayers.Control.ModifyFeature>, which sets the geometry property.
* Applications can set the originals of modified attributes in the
* attributes property. Note that applications have to check if this
* object and the attributes property is already created before using it.
* After a change made with ModifyFeature, this object could look like
*
* (code)
* {
* geometry: >Object
* }
* (end)
*
* When an application has made changes to feature attributes, it could
* have set the attributes to something like this:
*
* (code)
* {
* attributes: {
* myAttribute: "original"
* }
* }
* (end)
*
* Note that <OpenLayers.Format.WFST.v1> only checks for truthy values in
* *modified.geometry* and the attribute names in *modified.attributes*,
* but it is recommended to set the original values (and not just true) as
* attribute value, so applications could use this information to undo
* changes.
*/
modified: null,
/**
* Constructor: OpenLayers.Feature.Vector
* Create a vector feature.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>} The geometry that this feature
* represents.
* attributes - {Object} An optional object that will be mapped to the
* <attributes> property.
* style - {Object} An optional style object.
*/
initialize: function(geometry, attributes, style) {
OpenLayers.Feature.prototype.initialize.apply(this,
[null, null, attributes]);
this.lonlat = null;
this.geometry = geometry ? geometry : null;
this.state = null;
this.attributes = {};
if (attributes) {
this.attributes = OpenLayers.Util.extend(this.attributes,
attributes);
}
this.style = style ? style : null;
},
/**
* Method: destroy
* nullify references to prevent circular references and memory leaks
*/
destroy: function() {
if (this.layer) {
this.layer.removeFeatures(this);
this.layer = null;
}
this.geometry = null;
this.modified = null;
OpenLayers.Feature.prototype.destroy.apply(this, arguments);
},
/**
* Method: clone
* Create a clone of this vector feature. Does not set any non-standard
* properties.
*
* Returns:
* {<OpenLayers.Feature.Vector>} An exact clone of this vector feature.
*/
clone: function () {
return new OpenLayers.Feature.Vector(
this.geometry ? this.geometry.clone() : null,
this.attributes,
this.style);
},
/**
* Method: onScreen
* Determine whether the feature is within the map viewport. This method
* tests for an intersection between the geometry and the viewport
* bounds. If a more efficient but less precise geometry bounds
* intersection is desired, call the method with the boundsOnly
* parameter true.
*
* Parameters:
* boundsOnly - {Boolean} Only test whether a feature's bounds intersects
* the viewport bounds. Default is false. If false, the feature's
* geometry must intersect the viewport for onScreen to return true.
*
* Returns:
* {Boolean} The feature is currently visible on screen (optionally
* based on its bounds if boundsOnly is true).
*/
onScreen:function(boundsOnly) {
var onScreen = false;
if(this.layer && this.layer.map) {
var screenBounds = this.layer.map.getExtent();
if(boundsOnly) {
var featureBounds = this.geometry.getBounds();
onScreen = screenBounds.intersectsBounds(featureBounds);
} else {
var screenPoly = screenBounds.toGeometry();
onScreen = screenPoly.intersects(this.geometry);
}
}
return onScreen;
},
/**
* Method: getVisibility
* Determine whether the feature is displayed or not. It may not displayed
* because:
* - its style display property is set to 'none',
* - it doesn't belong to any layer,
* - the styleMap creates a symbolizer with display property set to 'none'
* for it,
* - the layer which it belongs to is not visible.
*
* Returns:
* {Boolean} The feature is currently displayed.
*/
getVisibility: function() {
return !(this.style && this.style.display == 'none' ||
!this.layer ||
this.layer && this.layer.styleMap &&
this.layer.styleMap.createSymbolizer(this, this.renderIntent).display == 'none' ||
this.layer && !this.layer.getVisibility());
},
/**
* Method: createMarker
* HACK - we need to decide if all vector features should be able to
* create markers
*
* Returns:
* {<OpenLayers.Marker>} For now just returns null
*/
createMarker: function() {
return null;
},
/**
* Method: destroyMarker
* HACK - we need to decide if all vector features should be able to
* delete markers
*
* If user overrides the createMarker() function, s/he should be able
* to also specify an alternative function for destroying it
*/
destroyMarker: function() {
// pass
},
/**
* Method: createPopup
* HACK - we need to decide if all vector features should be able to
* create popups
*
* Returns:
* {<OpenLayers.Popup>} For now just returns null
*/
createPopup: function() {
return null;
},
/**
* Method: atPoint
* Determins whether the feature intersects with the specified location.
*
* Parameters:
* lonlat - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
* object with a 'lon' and 'lat' properties.
* toleranceLon - {float} Optional tolerance in Geometric Coords
* toleranceLat - {float} Optional tolerance in Geographic Coords
*
* Returns:
* {Boolean} Whether or not the feature is at the specified location
*/
atPoint: function(lonlat, toleranceLon, toleranceLat) {
var atPoint = false;
if(this.geometry) {
atPoint = this.geometry.atPoint(lonlat, toleranceLon,
toleranceLat);
}
return atPoint;
},
/**
* Method: destroyPopup
* HACK - we need to decide if all vector features should be able to
* delete popups
*/
destroyPopup: function() {
// pass
},
/**
* Method: move
* Moves the feature and redraws it at its new location
*
* Parameters:
* location - {<OpenLayers.LonLat> or <OpenLayers.Pixel>} the
* location to which to move the feature.
*/
move: function(location) {
if(!this.layer || !this.geometry.move){
//do nothing if no layer or immoveable geometry
return undefined;
}
var pixel;
if (location.CLASS_NAME == "OpenLayers.LonLat") {
pixel = this.layer.getViewPortPxFromLonLat(location);
} else {
pixel = location;
}
var lastPixel = this.layer.getViewPortPxFromLonLat(this.geometry.getBounds().getCenterLonLat());
var res = this.layer.map.getResolution();
this.geometry.move(res * (pixel.x - lastPixel.x),
res * (lastPixel.y - pixel.y));
this.layer.drawFeature(this);
return lastPixel;
},
/**
* Method: toState
* Sets the new state
*
* Parameters:
* state - {String}
*/
toState: function(state) {
if (state == OpenLayers.State.UPDATE) {
switch (this.state) {
case OpenLayers.State.UNKNOWN:
case OpenLayers.State.DELETE:
this.state = state;
break;
case OpenLayers.State.UPDATE:
case OpenLayers.State.INSERT:
break;
}
} else if (state == OpenLayers.State.INSERT) {
switch (this.state) {
case OpenLayers.State.UNKNOWN:
break;
default:
this.state = state;
break;
}
} else if (state == OpenLayers.State.DELETE) {
switch (this.state) {
case OpenLayers.State.INSERT:
// the feature should be destroyed
break;
case OpenLayers.State.DELETE:
break;
case OpenLayers.State.UNKNOWN:
case OpenLayers.State.UPDATE:
this.state = state;
break;
}
} else if (state == OpenLayers.State.UNKNOWN) {
this.state = state;
}
},
CLASS_NAME: "OpenLayers.Feature.Vector"
});
/**
* Constant: OpenLayers.Feature.Vector.style
* OpenLayers features can have a number of style attributes. The 'default'
* style will typically be used if no other style is specified. These
* styles correspond for the most part, to the styling properties defined
* by the SVG standard.
* Information on fill properties: http://www.w3.org/TR/SVG/painting.html#FillProperties
* Information on stroke properties: http://www.w3.org/TR/SVG/painting.html#StrokeProperties
*
* Symbolizer properties:
* fill - {Boolean} Set to false if no fill is desired.
* fillColor - {String} Hex fill color. Default is "#ee9900".
* fillOpacity - {Number} Fill opacity (0-1). Default is 0.4
* stroke - {Boolean} Set to false if no stroke is desired.
* strokeColor - {String} Hex stroke color. Default is "#ee9900".
* strokeOpacity - {Number} Stroke opacity (0-1). Default is 1.
* strokeWidth - {Number} Pixel stroke width. Default is 1.
* strokeLinecap - {String} Stroke cap type. Default is "round". [butt | round | square]
* strokeDashstyle - {String} Stroke dash style. Default is "solid". [dot | dash | dashdot | longdash | longdashdot | solid]
* graphic - {Boolean} Set to false if no graphic is desired.
* pointRadius - {Number} Pixel point radius. Default is 6.
* pointerEvents - {String} Default is "visiblePainted".
* cursor - {String} Default is "".
* externalGraphic - {String} Url to an external graphic that will be used for rendering points.
* graphicWidth - {Number} Pixel width for sizing an external graphic.
* graphicHeight - {Number} Pixel height for sizing an external graphic.
* graphicOpacity - {Number} Opacity (0-1) for an external graphic.
* graphicXOffset - {Number} Pixel offset along the positive x axis for displacing an external graphic.
* graphicYOffset - {Number} Pixel offset along the positive y axis for displacing an external graphic.
* rotation - {Number} For point symbolizers, this is the rotation of a graphic in the clockwise direction about its center point (or any point off center as specified by graphicXOffset and graphicYOffset).
* graphicZIndex - {Number} The integer z-index value to use in rendering.
* graphicName - {String} Named graphic to use when rendering points. Supported values include "circle" (default),
* "square", "star", "x", "cross", "triangle".
* graphicTitle - {String} Tooltip when hovering over a feature. *deprecated*, use title instead
* title - {String} Tooltip when hovering over a feature. Not supported by the canvas renderer.
* backgroundGraphic - {String} Url to a graphic to be used as the background under an externalGraphic.
* backgroundGraphicZIndex - {Number} The integer z-index value to use in rendering the background graphic.
* backgroundXOffset - {Number} The x offset (in pixels) for the background graphic.
* backgroundYOffset - {Number} The y offset (in pixels) for the background graphic.
* backgroundHeight - {Number} The height of the background graphic. If not provided, the graphicHeight will be used.
* backgroundWidth - {Number} The width of the background width. If not provided, the graphicWidth will be used.
* label - {String} The text for an optional label. For browsers that use the canvas renderer, this requires either
* fillText or mozDrawText to be available.
* labelAlign - {String} Label alignment. This specifies the insertion point relative to the text. It is a string
* composed of two characters. The first character is for the horizontal alignment, the second for the vertical
* alignment. Valid values for horizontal alignment: "l"=left, "c"=center, "r"=right. Valid values for vertical
* alignment: "t"=top, "m"=middle, "b"=bottom. Example values: "lt", "cm", "rb". Default is "cm".
* labelXOffset - {Number} Pixel offset along the positive x axis for displacing the label. Not supported by the canvas renderer.
* labelYOffset - {Number} Pixel offset along the positive y axis for displacing the label. Not supported by the canvas renderer.
* labelSelect - {Boolean} If set to true, labels will be selectable using SelectFeature or similar controls.
* Default is false.
* labelOutlineColor - {String} The color of the label outline. Default is 'white'. Only supported by the canvas & SVG renderers.
* labelOutlineWidth - {Number} The width of the label outline. Default is 3, set to 0 or null to disable. Only supported by the SVG renderers.
* labelOutlineOpacity - {Number} The opacity (0-1) of the label outline. Default is fontOpacity. Only supported by the canvas & SVG renderers.
* fontColor - {String} The font color for the label, to be provided like CSS.
* fontOpacity - {Number} Opacity (0-1) for the label
* fontFamily - {String} The font family for the label, to be provided like in CSS.
* fontSize - {String} The font size for the label, to be provided like in CSS.
* fontStyle - {String} The font style for the label, to be provided like in CSS.
* fontWeight - {String} The font weight for the label, to be provided like in CSS.
* display - {String} Symbolizers will have no effect if display is set to "none". All other values have no effect.
*/
OpenLayers.Feature.Vector.style = {
'default': {
fillColor: "#ee9900",
fillOpacity: 0.4,
hoverFillColor: "white",
hoverFillOpacity: 0.8,
strokeColor: "#ee9900",
strokeOpacity: 1,
strokeWidth: 1,
strokeLinecap: "round",
strokeDashstyle: "solid",
hoverStrokeColor: "red",
hoverStrokeOpacity: 1,
hoverStrokeWidth: 0.2,
pointRadius: 6,
hoverPointRadius: 1,
hoverPointUnit: "%",
pointerEvents: "visiblePainted",
cursor: "inherit",
fontColor: "#000000",
labelAlign: "cm",
labelOutlineColor: "white",
labelOutlineWidth: 3
},
'select': {
fillColor: "blue",
fillOpacity: 0.4,
hoverFillColor: "white",
hoverFillOpacity: 0.8,
strokeColor: "blue",
strokeOpacity: 1,
strokeWidth: 2,
strokeLinecap: "round",
strokeDashstyle: "solid",
hoverStrokeColor: "red",
hoverStrokeOpacity: 1,
hoverStrokeWidth: 0.2,
pointRadius: 6,
hoverPointRadius: 1,
hoverPointUnit: "%",
pointerEvents: "visiblePainted",
cursor: "pointer",
fontColor: "#000000",
labelAlign: "cm",
labelOutlineColor: "white",
labelOutlineWidth: 3
},
'temporary': {
fillColor: "#66cccc",
fillOpacity: 0.2,
hoverFillColor: "white",
hoverFillOpacity: 0.8,
strokeColor: "#66cccc",
strokeOpacity: 1,
strokeLinecap: "round",
strokeWidth: 2,
strokeDashstyle: "solid",
hoverStrokeColor: "red",
hoverStrokeOpacity: 1,
hoverStrokeWidth: 0.2,
pointRadius: 6,
hoverPointRadius: 1,
hoverPointUnit: "%",
pointerEvents: "visiblePainted",
cursor: "inherit",
fontColor: "#000000",
labelAlign: "cm",
labelOutlineColor: "white",
labelOutlineWidth: 3
},
'delete': {
display: "none"
}
};
/* ======================================================================
OpenLayers/Style.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/BaseTypes/Class.js
* @requires OpenLayers/Util.js
* @requires OpenLayers/Feature/Vector.js
*/
/**
* Class: OpenLayers.Style
* This class represents a UserStyle obtained
* from a SLD, containing styling rules.
*/
OpenLayers.Style = OpenLayers.Class({
/**
* Property: id
* {String} A unique id for this session.
*/
id: null,
/**
* APIProperty: name
* {String}
*/
name: null,
/**
* Property: title
* {String} Title of this style (set if included in SLD)
*/
title: null,
/**
* Property: description
* {String} Description of this style (set if abstract is included in SLD)
*/
description: null,
/**
* APIProperty: layerName
* {<String>} name of the layer that this style belongs to, usually
* according to the NamedLayer attribute of an SLD document.
*/
layerName: null,
/**
* APIProperty: isDefault
* {Boolean}
*/
isDefault: false,
/**
* Property: rules
* {Array(<OpenLayers.Rule>)}
*/
rules: null,
/**
* APIProperty: context
* {Object} An optional object with properties that symbolizers' property
* values should be evaluated against. If no context is specified,
* feature.attributes will be used
*/
context: null,
/**
* Property: defaultStyle
* {Object} hash of style properties to use as default for merging
* rule-based style symbolizers onto. If no rules are defined,
* createSymbolizer will return this style. If <defaultsPerSymbolizer> is set to
* true, the defaultStyle will only be taken into account if there are
* rules defined.
*/
defaultStyle: null,
/**
* Property: defaultsPerSymbolizer
* {Boolean} If set to true, the <defaultStyle> will extend the symbolizer
* of every rule. Properties of the <defaultStyle> will also be used to set
* missing symbolizer properties if the symbolizer has stroke, fill or
* graphic set to true. Default is false.
*/
defaultsPerSymbolizer: false,
/**
* Property: propertyStyles
* {Hash of Boolean} cache of style properties that need to be parsed for
* propertyNames. Property names are keys, values won't be used.
*/
propertyStyles: null,
/**
* Constructor: OpenLayers.Style
* Creates a UserStyle.
*
* Parameters:
* style - {Object} Optional hash of style properties that will be
* used as default style for this style object. This style
* applies if no rules are specified. Symbolizers defined in
* rules will extend this default style.
* options - {Object} An optional object with properties to set on the
* style.
*
* Valid options:
* rules - {Array(<OpenLayers.Rule>)} List of rules to be added to the
* style.
*
* Returns:
* {<OpenLayers.Style>}
*/
initialize: function(style, options) {
OpenLayers.Util.extend(this, options);
this.rules = [];
if(options && options.rules) {
this.addRules(options.rules);
}
// use the default style from OpenLayers.Feature.Vector if no style
// was given in the constructor
this.setDefaultStyle(style ||
OpenLayers.Feature.Vector.style["default"]);
this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
},
/**
* APIMethod: destroy
* nullify references to prevent circular references and memory leaks
*/
destroy: function() {
for (var i=0, len=this.rules.length; i<len; i++) {
this.rules[i].destroy();
this.rules[i] = null;
}
this.rules = null;
this.defaultStyle = null;
},
/**
* Method: createSymbolizer
* creates a style by applying all feature-dependent rules to the base
* style.
*
* Parameters:
* feature - {<OpenLayers.Feature>} feature to evaluate rules for
*
* Returns:
* {Object} symbolizer hash
*/
createSymbolizer: function(feature) {
var style = this.defaultsPerSymbolizer ? {} : this.createLiterals(
OpenLayers.Util.extend({}, this.defaultStyle), feature);
var rules = this.rules;
var rule, context;
var elseRules = [];
var appliedRules = false;
for(var i=0, len=rules.length; i<len; i++) {
rule = rules[i];
// does the rule apply?
var applies = rule.evaluate(feature);
if(applies) {
if(rule instanceof OpenLayers.Rule && rule.elseFilter) {
elseRules.push(rule);
} else {
appliedRules = true;
this.applySymbolizer(rule, style, feature);
}
}
}
// if no other rules apply, apply the rules with else filters
if(appliedRules == false && elseRules.length > 0) {
appliedRules = true;
for(var i=0, len=elseRules.length; i<len; i++) {
this.applySymbolizer(elseRules[i], style, feature);
}
}
// don't display if there were rules but none applied
if(rules.length > 0 && appliedRules == false) {
style.display = "none";
}
if (style.label != null && typeof style.label !== "string") {
style.label = String(style.label);
}
return style;
},
/**
* Method: applySymbolizer
*
* Parameters:
* rule - {<OpenLayers.Rule>}
* style - {Object}
* feature - {<OpenLayer.Feature.Vector>}
*
* Returns:
* {Object} A style with new symbolizer applied.
*/
applySymbolizer: function(rule, style, feature) {
var symbolizerPrefix = feature.geometry ?
this.getSymbolizerPrefix(feature.geometry) :
OpenLayers.Style.SYMBOLIZER_PREFIXES[0];
var symbolizer = rule.symbolizer[symbolizerPrefix] || rule.symbolizer;
if(this.defaultsPerSymbolizer === true) {
var defaults = this.defaultStyle;
OpenLayers.Util.applyDefaults(symbolizer, {
pointRadius: defaults.pointRadius
});
if(symbolizer.stroke === true || symbolizer.graphic === true) {
OpenLayers.Util.applyDefaults(symbolizer, {
strokeWidth: defaults.strokeWidth,
strokeColor: defaults.strokeColor,
strokeOpacity: defaults.strokeOpacity,
strokeDashstyle: defaults.strokeDashstyle,
strokeLinecap: defaults.strokeLinecap
});
}
if(symbolizer.fill === true || symbolizer.graphic === true) {
OpenLayers.Util.applyDefaults(symbolizer, {
fillColor: defaults.fillColor,
fillOpacity: defaults.fillOpacity
});
}
if(symbolizer.graphic === true) {
OpenLayers.Util.applyDefaults(symbolizer, {
pointRadius: this.defaultStyle.pointRadius,
externalGraphic: this.defaultStyle.externalGraphic,
graphicName: this.defaultStyle.graphicName,
graphicOpacity: this.defaultStyle.graphicOpacity,
graphicWidth: this.defaultStyle.graphicWidth,
graphicHeight: this.defaultStyle.graphicHeight,
graphicXOffset: this.defaultStyle.graphicXOffset,
graphicYOffset: this.defaultStyle.graphicYOffset
});
}
}
// merge the style with the current style
return this.createLiterals(
OpenLayers.Util.extend(style, symbolizer), feature);
},
/**
* Method: createLiterals
* creates literals for all style properties that have an entry in
* <this.propertyStyles>.
*
* Parameters:
* style - {Object} style to create literals for. Will be modified
* inline.
* feature - {Object}
*
* Returns:
* {Object} the modified style
*/
createLiterals: function(style, feature) {
var context = OpenLayers.Util.extend({}, feature.attributes || feature.data);
OpenLayers.Util.extend(context, this.context);
for (var i in this.propertyStyles) {
style[i] = OpenLayers.Style.createLiteral(style[i], context, feature, i);
}
return style;
},
/**
* Method: findPropertyStyles
* Looks into all rules for this style and the defaultStyle to collect
* all the style hash property names containing ${...} strings that have
* to be replaced using the createLiteral method before returning them.
*
* Returns:
* {Object} hash of property names that need createLiteral parsing. The
* name of the property is the key, and the value is true;
*/
findPropertyStyles: function() {
var propertyStyles = {};
// check the default style
var style = this.defaultStyle;
this.addPropertyStyles(propertyStyles, style);
// walk through all rules to check for properties in their symbolizer
var rules = this.rules;
var symbolizer, value;
for (var i=0, len=rules.length; i<len; i++) {
symbolizer = rules[i].symbolizer;
for (var key in symbolizer) {
value = symbolizer[key];
if (typeof value == "object") {
// symbolizer key is "Point", "Line" or "Polygon"
this.addPropertyStyles(propertyStyles, value);
} else {
// symbolizer is a hash of style properties
this.addPropertyStyles(propertyStyles, symbolizer);
break;
}
}
}
return propertyStyles;
},
/**
* Method: addPropertyStyles
*
* Parameters:
* propertyStyles - {Object} hash to add new property styles to. Will be
* modified inline
* symbolizer - {Object} search this symbolizer for property styles
*
* Returns:
* {Object} propertyStyles hash
*/
addPropertyStyles: function(propertyStyles, symbolizer) {
var property;
for (var key in symbolizer) {
property = symbolizer[key];
if (typeof property == "string" &&
property.match(/\$\{\w+\}/)) {
propertyStyles[key] = true;
}
}
return propertyStyles;
},
/**
* APIMethod: addRules
* Adds rules to this style.
*
* Parameters:
* rules - {Array(<OpenLayers.Rule>)}
*/
addRules: function(rules) {
Array.prototype.push.apply(this.rules, rules);
this.propertyStyles = this.findPropertyStyles();
},
/**
* APIMethod: setDefaultStyle
* Sets the default style for this style object.
*
* Parameters:
* style - {Object} Hash of style properties
*/
setDefaultStyle: function(style) {
this.defaultStyle = style;
this.propertyStyles = this.findPropertyStyles();
},
/**
* Method: getSymbolizerPrefix
* Returns the correct symbolizer prefix according to the
* geometry type of the passed geometry
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
*
* Returns:
* {String} key of the according symbolizer
*/
getSymbolizerPrefix: function(geometry) {
var prefixes = OpenLayers.Style.SYMBOLIZER_PREFIXES;
for (var i=0, len=prefixes.length; i<len; i++) {
if (geometry.CLASS_NAME.indexOf(prefixes[i]) != -1) {
return prefixes[i];
}
}
},
/**
* APIMethod: clone
* Clones this style.
*
* Returns:
* {<OpenLayers.Style>} Clone of this style.
*/
clone: function() {
var options = OpenLayers.Util.extend({}, this);
// clone rules
if(this.rules) {
options.rules = [];
for(var i=0, len=this.rules.length; i<len; ++i) {
options.rules.push(this.rules[i].clone());
}
}
// clone context
options.context = this.context && OpenLayers.Util.extend({}, this.context);
//clone default style
var defaultStyle = OpenLayers.Util.extend({}, this.defaultStyle);
return new OpenLayers.Style(defaultStyle, options);
},
CLASS_NAME: "OpenLayers.Style"
});
/**
* Function: createLiteral
* converts a style value holding a combination of PropertyName and Literal
* into a Literal, taking the property values from the passed features.
*
* Parameters:
* value - {String} value to parse. If this string contains a construct like
* "foo ${bar}", then "foo " will be taken as literal, and "${bar}"
* will be replaced by the value of the "bar" attribute of the passed
* feature.
* context - {Object} context to take attribute values from
* feature - {<OpenLayers.Feature.Vector>} optional feature to pass to
* <OpenLayers.String.format> for evaluating functions in the
* context.
* property - {String} optional, name of the property for which the literal is
* being created for evaluating functions in the context.
*
* Returns:
* {String} the parsed value. In the example of the value parameter above, the
* result would be "foo valueOfBar", assuming that the passed feature has an
* attribute named "bar" with the value "valueOfBar".
*/
OpenLayers.Style.createLiteral = function(value, context, feature, property) {
if (typeof value == "string" && value.indexOf("${") != -1) {
value = OpenLayers.String.format(value, context, [feature, property]);
value = (isNaN(value) || !value) ? value : parseFloat(value);
}
return value;
};
/**
* Constant: OpenLayers.Style.SYMBOLIZER_PREFIXES
* {Array} prefixes of the sld symbolizers. These are the
* same as the main geometry types
*/
OpenLayers.Style.SYMBOLIZER_PREFIXES = ['Point', 'Line', 'Polygon', 'Text',
'Raster'];
/* ======================================================================
OpenLayers/StyleMap.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/BaseTypes/Class.js
* @requires OpenLayers/Style.js
* @requires OpenLayers/Feature/Vector.js
*/
/**
* Class: OpenLayers.StyleMap
*/
OpenLayers.StyleMap = OpenLayers.Class({
/**
* Property: styles
* {Object} Hash of {<OpenLayers.Style>}, keyed by names of well known
* rendering intents (e.g. "default", "temporary", "select", "delete").
*/
styles: null,
/**
* Property: extendDefault
* {Boolean} if true, every render intent will extend the symbolizers
* specified for the "default" intent at rendering time. Otherwise, every
* rendering intent will be treated as a completely independent style.
*/
extendDefault: true,
/**
* Constructor: OpenLayers.StyleMap
*
* Parameters:
* style - {Object} Optional. Either a style hash, or a style object, or
* a hash of style objects (style hashes) keyed by rendering
* intent. If just one style hash or style object is passed,
* this will be used for all known render intents (default,
* select, temporary)
* options - {Object} optional hash of additional options for this
* instance
*/
initialize: function (style, options) {
this.styles = {
"default": new OpenLayers.Style(
OpenLayers.Feature.Vector.style["default"]),
"select": new OpenLayers.Style(
OpenLayers.Feature.Vector.style["select"]),
"temporary": new OpenLayers.Style(
OpenLayers.Feature.Vector.style["temporary"]),
"delete": new OpenLayers.Style(
OpenLayers.Feature.Vector.style["delete"])
};
// take whatever the user passed as style parameter and convert it
// into parts of stylemap.
if(style instanceof OpenLayers.Style) {
// user passed a style object
this.styles["default"] = style;
this.styles["select"] = style;
this.styles["temporary"] = style;
this.styles["delete"] = style;
} else if(typeof style == "object") {
for(var key in style) {
if(style[key] instanceof OpenLayers.Style) {
// user passed a hash of style objects
this.styles[key] = style[key];
} else if(typeof style[key] == "object") {
// user passsed a hash of style hashes
this.styles[key] = new OpenLayers.Style(style[key]);
} else {
// user passed a style hash (i.e. symbolizer)
this.styles["default"] = new OpenLayers.Style(style);
this.styles["select"] = new OpenLayers.Style(style);
this.styles["temporary"] = new OpenLayers.Style(style);
this.styles["delete"] = new OpenLayers.Style(style);
break;
}
}
}
OpenLayers.Util.extend(this, options);
},
/**
* Method: destroy
*/
destroy: function() {
for(var key in this.styles) {
this.styles[key].destroy();
}
this.styles = null;
},
/**
* Method: createSymbolizer
* Creates the symbolizer for a feature for a render intent.
*
* Parameters:
* feature - {<OpenLayers.Feature>} The feature to evaluate the rules
* of the intended style against.
* intent - {String} The intent determines the symbolizer that will be
* used to draw the feature. Well known intents are "default"
* (for just drawing the features), "select" (for selected
* features) and "temporary" (for drawing features).
*
* Returns:
* {Object} symbolizer hash
*/
createSymbolizer: function(feature, intent) {
if(!feature) {
feature = new OpenLayers.Feature.Vector();
}
if(!this.styles[intent]) {
intent = "default";
}
feature.renderIntent = intent;
var defaultSymbolizer = {};
if(this.extendDefault && intent != "default") {
defaultSymbolizer = this.styles["default"].createSymbolizer(feature);
}
return OpenLayers.Util.extend(defaultSymbolizer,
this.styles[intent].createSymbolizer(feature));
},
/**
* Method: addUniqueValueRules
* Convenience method to create comparison rules for unique values of a
* property. The rules will be added to the style object for a specified
* rendering intent. This method is a shortcut for creating something like
* the "unique value legends" familiar from well known desktop GIS systems
*
* Parameters:
* renderIntent - {String} rendering intent to add the rules to
* property - {String} values of feature attributes to create the
* rules for
* symbolizers - {Object} Hash of symbolizers, keyed by the desired
* property values
* context - {Object} An optional object with properties that
* symbolizers' property values should be evaluated
* against. If no context is specified, feature.attributes
* will be used
*/
addUniqueValueRules: function(renderIntent, property, symbolizers, context) {
var rules = [];
for (var value in symbolizers) {
rules.push(new OpenLayers.Rule({
symbolizer: symbolizers[value],
context: context,
filter: new OpenLayers.Filter.Comparison({
type: OpenLayers.Filter.Comparison.EQUAL_TO,
property: property,
value: value
})
}));
}
this.styles[renderIntent].addRules(rules);
},
CLASS_NAME: "OpenLayers.StyleMap"
});
/* ======================================================================
OpenLayers/Rule.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published 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
* {<OpenLayers.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:
* {<OpenLayers.Rule>}
*/
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 - {<OpenLayers.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
*
* Parameters:
* feature - {<OpenLayers.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:
* {<OpenLayers.Rule>} 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<len; ++i) {
options.symbolizers[i] = this.symbolizers[i].clone();
}
} else {
// clone symbolizer
options.symbolizer = {};
var value, type;
for(var key in this.symbolizer) {
value = this.symbolizer[key];
type = typeof value;
if(type === "object") {
options.symbolizer[key] = OpenLayers.Util.extend({}, value);
} else if(type === "string") {
options.symbolizer[key] = value;
}
}
}
// clone filter
options.filter = this.filter && this.filter.clone();
// clone context
options.context = this.context && OpenLayers.Util.extend({}, this.context);
return new OpenLayers.Rule(options);
},
CLASS_NAME: "OpenLayers.Rule"
});
/* ======================================================================
OpenLayers/Events.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published 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: isTouchEvent
* Determine whether the event was triggered by a touch
*
* Parameters:
* evt - {Event}
*
* Returns:
* {Boolean}
*/
isTouchEvent: function(evt) {
return ("" + evt.type).indexOf("touch") === 0 || (
"pointerType" in evt && (
evt.pointerType === evt.MSPOINTER_TYPE_TOUCH /*IE10 pointer*/ ||
evt.pointerType === "touch" /*W3C pointer*/));
},
/**
* 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"
],
/**
* Constant: standard pointer model
* {string}
*/
TOUCH_MODEL_POINTER: "pointer",
/**
* Constant: prefixed pointer model (IE10)
* {string}
*/
TOUCH_MODEL_MSPOINTER: "MSPointer",
/**
* Constant: legacy touch model
* {string}
*/
TOUCH_MODEL_TOUCH: "touch",
/**
* Property: listeners
* {Object} Hashtable of Array(Function): events listener functions
*/
listeners: null,
/**
* Property: object
* {Object} the code object issuing application events
*/
object: null,
/**
* Property: element
* {DOMElement} the DOM element receiving browser events
*/
element: null,
/**
* Property: eventHandler
* {Function} bound event handler attached to elements
*/
eventHandler: null,
/**
* APIProperty: fallThrough
* {Boolean}
*/
fallThrough: null,
/**
* APIProperty: includeXY
* {Boolean} Should the .xy property automatically be created for browser
* mouse events? In general, this should be false. If it is true, then
* mouse events will automatically generate a '.xy' property on the
* event object that is passed. (Prior to OpenLayers 2.7, this was true
* by default.) Otherwise, you can call the getMousePosition on the
* relevant events handler on the object available via the 'evt.object'
* property of the evt object. So, for most events, you can call:
* function named(evt) {
* this.xy = this.object.events.getMousePosition(evt)
* }
*
* This option typically defaults to false for performance reasons:
* when creating an events object whose primary purpose is to manage
* relatively positioned mouse events within a div, it may make
* sense to set it to true.
*
* This option is also used to control whether the events object caches
* offsets. If this is false, it will not: the reason for this is that
* it is only expected to be called many times if the includeXY property
* is set to true. If you set this to true, you are expected to clear
* the offset cache manually (using this.clearMouseCache()) if:
* the border of the element changes
* the location of the element in the page changes
*/
includeXY: false,
/**
* APIProperty: extensions
* {Object} Event extensions registered with this instance. Keys are
* event types, values are {OpenLayers.Events.*} extension instances or
* {Boolean} for events that an instantiated extension provides in
* addition to the one it was created for.
*
* Extensions create an event in addition to browser events, which usually
* fires when a sequence of browser events is completed. Extensions are
* automatically instantiated when a listener is registered for an event
* provided by an extension.
*
* Extensions are created in the <OpenLayers.Events> namespace using
* <OpenLayers.Class>, and named after the event they provide.
* The constructor receives the target <OpenLayers.Events> instance as
* argument. Extensions that need to capture browser events before they
* propagate can register their listeners events using <register>, with
* {extension: true} as 4th argument.
*
* If an extension creates more than one event, an alias for each event
* type should be created and reference the same class. The constructor
* should set a reference in the target's extensions registry to itself.
*
* Below is a minimal extension that provides the "foostart" and "fooend"
* event types, which replace the native "click" event type if clicked on
* an element with the css class "foo":
*
* (code)
* OpenLayers.Events.foostart = OpenLayers.Class({
* initialize: function(target) {
* this.target = target;
* this.target.register("click", this, this.doStuff, {extension: true});
* // only required if extension provides more than one event type
* this.target.extensions["foostart"] = true;
* this.target.extensions["fooend"] = true;
* },
* destroy: function() {
* var target = this.target;
* target.unregister("click", this, this.doStuff);
* delete this.target;
* // only required if extension provides more than one event type
* delete target.extensions["foostart"];
* delete target.extensions["fooend"];
* },
* doStuff: function(evt) {
* var propagate = true;
* if (OpenLayers.Event.element(evt).className === "foo") {
* propagate = false;
* var target = this.target;
* target.triggerEvent("foostart");
* window.setTimeout(function() {
* target.triggerEvent("fooend");
* }, 1000);
* }
* return propagate;
* }
* });
* // only required if extension provides more than one event type
* OpenLayers.Events.fooend = OpenLayers.Events.foostart;
* (end)
*
*/
extensions: null,
/**
* Property: extensionCount
* {Object} Keys are event types (like in <listeners>), values are the
* number of extension listeners for each event type.
*/
extensionCount: null,
/**
* Method: clearMouseListener
* A version of <clearMouseCache> that is bound to this instance so that
* it can be used with <OpenLayers.Event.observe> and
* <OpenLayers.Event.stopObserving>.
*/
clearMouseListener: null,
/**
* Constructor: OpenLayers.Events
* Construct an OpenLayers.Events object.
*
* Parameters:
* object - {Object} The js object to which this Events object is being added
* element - {DOMElement} A dom element to respond to browser events
* eventTypes - {Array(String)} Deprecated. Array of custom application
* events. A listener may be registered for any named event, regardless
* of the values provided here.
* fallThrough - {Boolean} Allow events to fall through after these have
* been handled?
* options - {Object} Options for the events object.
*/
initialize: function (object, element, eventTypes, fallThrough, options) {
OpenLayers.Util.extend(this, options);
this.object = object;
this.fallThrough = fallThrough;
this.listeners = {};
this.extensions = {};
this.extensionCount = {};
this._pointerTouches = [];
// 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 touchModel = this.getTouchModel();
var type;
for (var i = 0, len = this.BROWSER_EVENTS.length; i < len; i++) {
type = this.BROWSER_EVENTS[i];
// register the event cross-browser
if ((touchModel === this.TOUCH_MODEL_POINTER ||
touchModel === this.TOUCH_MODEL_MSPOINTER) &&
type.indexOf('touch') === 0) {
this.addPointerTouchListener(element, type, this.eventHandler);
} else {
OpenLayers.Event.observe(element, type, this.eventHandler);
}
}
// disable dragstart in IE so that mousedown/move/up works normally
OpenLayers.Event.observe(element, "dragstart", OpenLayers.Event.stop);
},
/**
* APIMethod: on
* Convenience method for registering listeners with a common scope.
* Internally, this method calls <register> as shown in the examples
* below.
*
* Example use:
* (code)
* // register a single listener for the "loadstart" event
* events.on({"loadstart": loadStartListener});
*
* // this is equivalent to the following
* events.register("loadstart", undefined, loadStartListener);
*
* // register multiple listeners to be called with the same `this` object
* events.on({
* "loadstart": loadStartListener,
* "loadend": loadEndListener,
* scope: object
* });
*
* // this is equivalent to the following
* events.register("loadstart", object, loadStartListener);
* events.register("loadend", object, loadEndListener);
* (end)
*
* Parameters:
* object - {Object}
*/
on: function(object) {
for(var type in object) {
if(type != "scope" && object.hasOwnProperty(type)) {
this.register(type, object.scope, object[type]);
}
}
},
/**
* APIMethod: register
* Register an event on the events object.
*
* When the event is triggered, the 'func' function will be called, in the
* context of 'obj'. Imagine we were to register an event, specifying an
* OpenLayers.Bounds Object as 'obj'. When the event is triggered, the
* context in the callback function will be our Bounds object. This means
* that within our callback function, we can access the properties and
* methods of the Bounds object through the "this" variable. So our
* callback could execute something like:
* : leftStr = "Left: " + this.left;
*
* or
*
* : centerStr = "Center: " + this.getCenterLonLat();
*
* Parameters:
* type - {String} Name of the event to register
* obj - {Object} The object to bind the context to for the callback#.
* If no object is specified, default is the Events's 'object' property.
* func - {Function} The callback function. If no callback is
* specified, this function does nothing.
* priority - {Boolean|Object} If true, adds the new listener to the
* *front* of the events queue instead of to the end.
*
* Valid options for priority:
* extension - {Boolean} If true, then the event will be registered as
* extension event. Extension events are handled before all other
* events.
*/
register: function (type, obj, func, priority) {
if (type in OpenLayers.Events && !this.extensions[type]) {
this.extensions[type] = new OpenLayers.Events[type](this);
}
if (func != null) {
if (obj == null) {
obj = this.object;
}
var listeners = this.listeners[type];
if (!listeners) {
listeners = [];
this.listeners[type] = listeners;
this.extensionCount[type] = 0;
}
var listener = {obj: obj, func: func};
if (priority) {
listeners.splice(this.extensionCount[type], 0, listener);
if (typeof priority === "object" && priority.extension) {
this.extensionCount[type]++;
}
} else {
listeners.push(listener);
}
}
},
/**
* APIMethod: registerPriority
* Same as register() but adds the new listener to the *front* of the
* events queue instead of to the end.
*
* TODO: get rid of this in 3.0 - Decide whether listeners should be
* called in the order they were registered or in reverse order.
*
*
* Parameters:
* type - {String} Name of the event to register
* obj - {Object} The object to bind the context to for the callback#.
* If no object is specified, default is the Events's
* 'object' property.
* func - {Function} The callback function. If no callback is
* specified, this function does nothing.
*/
registerPriority: function (type, obj, func) {
this.register(type, obj, func, true);
},
/**
* APIMethod: un
* Convenience method for unregistering listeners with a common scope.
* Internally, this method calls <unregister> as shown in the examples
* below.
*
* Example use:
* (code)
* // unregister a single listener for the "loadstart" event
* events.un({"loadstart": loadStartListener});
*
* // this is equivalent to the following
* events.unregister("loadstart", undefined, loadStartListener);
*
* // unregister multiple listeners with the same `this` object
* events.un({
* "loadstart": loadStartListener,
* "loadend": loadEndListener,
* scope: object
* });
*
* // this is equivalent to the following
* events.unregister("loadstart", object, loadStartListener);
* events.unregister("loadend", object, loadEndListener);
* (end)
*/
un: function(object) {
for(var type in object) {
if(type != "scope" && object.hasOwnProperty(type)) {
this.unregister(type, object.scope, object[type]);
}
}
},
/**
* APIMethod: unregister
*
* Parameters:
* type - {String}
* obj - {Object} If none specified, defaults to this.object
* func - {Function}
*/
unregister: function (type, obj, func) {
if (obj == null) {
obj = this.object;
}
var listeners = this.listeners[type];
if (listeners != null) {
for (var i=0, len=listeners.length; i<len; i++) {
if (listeners[i].obj == obj && listeners[i].func == func) {
listeners.splice(i, 1);
break;
}
}
}
},
/**
* Method: remove
* Remove all listeners for a given event type. If type is not registered,
* does nothing.
*
* Parameters:
* type - {String}
*/
remove: function(type) {
if (this.listeners[type] != null) {
this.listeners[type] = [];
}
},
/**
* APIMethod: triggerEvent
* Trigger a specified registered event.
*
* Parameters:
* type - {String}
* evt - {Event || Object} will be passed to the listeners.
*
* Returns:
* {Boolean} The last listener return. If a listener returns false, the
* chain of listeners will stop getting called.
*/
triggerEvent: function (type, evt) {
var listeners = this.listeners[type];
// fast path
if(!listeners || listeners.length == 0) {
return undefined;
}
// prep evt object with object & div references
if (evt == null) {
evt = {};
}
evt.object = this.object;
evt.element = this.element;
if(!evt.type) {
evt.type = type;
}
// execute all callbacks registered for specified type
// get a clone of the listeners array to
// allow for splicing during callbacks
listeners = listeners.slice();
var continueChain;
for (var i=0, len=listeners.length; i<len; i++) {
var callback = listeners[i];
// bind the context to callback.obj
continueChain = callback.func.apply(callback.obj, [evt]);
if ((continueChain != undefined) && (continueChain == false)) {
// if callback returns false, execute no more callbacks.
break;
}
}
// don't fall through to other DOM elements
if (!this.fallThrough) {
OpenLayers.Event.stop(evt, true);
}
return continueChain;
},
/**
* Method: handleBrowserEvent
* Basically just a wrapper to the triggerEvent() function, but takes
* care to set a property 'xy' on the event with the current mouse
* position.
*
* Parameters:
* evt - {Event}
*/
handleBrowserEvent: function (evt) {
var type = evt.type, listeners = this.listeners[type];
if(!listeners || listeners.length == 0) {
// no one's listening, bail out
return;
}
// add clientX & clientY to all events - corresponds to average x, y
var touches = evt.touches;
if (touches && touches[0]) {
var x = 0;
var y = 0;
var num = touches.length;
var touch;
for (var i=0; i<num; ++i) {
touch = this.getTouchClientXY(touches[i]);
x += touch.clientX;
y += touch.clientY;
}
evt.clientX = x / num;
evt.clientY = y / num;
}
if (this.includeXY) {
evt.xy = this.getMousePosition(evt);
}
this.triggerEvent(type, evt);
},
/**
* Method: getTouchClientXY
* WebKit has a few bugs for clientX/clientY. This method detects them
* and calculate the correct values.
*
* Parameters:
* evt - {Touch} a Touch object from a TouchEvent
*
* Returns:
* {Object} An object with only clientX and clientY properties with the
* calculated values.
*/
getTouchClientXY: function (evt) {
// olMochWin is to override window, used for testing
var win = window.olMockWin || window,
winPageX = win.pageXOffset,
winPageY = win.pageYOffset,
x = evt.clientX,
y = evt.clientY;
if (evt.pageY === 0 && Math.floor(y) > Math.floor(evt.pageY) ||
evt.pageX === 0 && Math.floor(x) > Math.floor(evt.pageX)) {
// iOS4 include scroll offset in clientX/Y
x = x - winPageX;
y = y - winPageY;
} else if (y < (evt.pageY - winPageY) || x < (evt.pageX - winPageX) ) {
// Some Android browsers have totally bogus values for clientX/Y
// when scrolling/zooming a page
x = evt.pageX - winPageX;
y = evt.pageY - winPageY;
}
evt.olClientX = x;
evt.olClientY = y;
return {
clientX: x,
clientY: y
};
},
/**
* APIMethod: clearMouseCache
* Clear cached data about the mouse position. This should be called any
* time the element that events are registered on changes position
* within the page.
*/
clearMouseCache: function() {
this.element.scrolls = null;
this.element.lefttop = null;
this.element.offsets = null;
},
/**
* Method: getMousePosition
*
* Parameters:
* evt - {Event}
*
* Returns:
* {<OpenLayers.Pixel>} The current xy coordinate of the mouse, adjusted
* for offsets
*/
getMousePosition: function (evt) {
if (!this.includeXY) {
this.clearMouseCache();
} else if (!this.element.hasScrollEvent) {
OpenLayers.Event.observe(window, "scroll", this.clearMouseListener);
this.element.hasScrollEvent = true;
}
if (!this.element.scrolls) {
var viewportElement = OpenLayers.Util.getViewportElement();
this.element.scrolls = [
window.pageXOffset || viewportElement.scrollLeft,
window.pageYOffset || viewportElement.scrollTop
];
}
if (!this.element.lefttop) {
this.element.lefttop = [
(document.documentElement.clientLeft || 0),
(document.documentElement.clientTop || 0)
];
}
if (!this.element.offsets) {
this.element.offsets = OpenLayers.Util.pagePosition(this.element);
}
return new OpenLayers.Pixel(
(evt.clientX + this.element.scrolls[0]) - this.element.offsets[0]
- this.element.lefttop[0],
(evt.clientY + this.element.scrolls[1]) - this.element.offsets[1]
- this.element.lefttop[1]
);
},
/**
* Method: getTouchModel
* Get the touch model currently in use.
*
* This is cached on OpenLayers.Events as _TOUCH_MODEL
*
* Returns:
* {string} The current touch model (TOUCH_MODEL_xxx), null if none
*/
getTouchModel: function() {
if (!("_TOUCH_MODEL" in OpenLayers.Events)) {
OpenLayers.Events._TOUCH_MODEL =
(window.PointerEvent && "pointer") ||
(window.MSPointerEvent && "MSPointer") ||
(("ontouchdown" in document) && "touch") ||
null;
}
return OpenLayers.Events._TOUCH_MODEL;
},
/**
* Method: addPointerTouchListener
*
* Parameters:
* element - {DOMElement} The DOM element to register the listener on
* type - {String} The event type
* handler - {Function} the handler
*/
addPointerTouchListener: function (element, type, handler) {
var eventHandler = this.eventHandler;
var touches = this._pointerTouches;
function pointerHandler(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.addPointerTouchListenerStart(element, type, pointerHandler);
case 'touchend':
return this.addPointerTouchListenerEnd(element, type, pointerHandler);
case 'touchmove':
return this.addPointerTouchListenerMove(element, type, pointerHandler);
default:
throw 'Unknown touch event type';
}
},
/**
* Method: addPointerTouchListenerStart
*
* Parameters:
* element - {DOMElement} The DOM element to register the listener on
* type - {String} The event type
* handler - {Function} the handler
*/
addPointerTouchListenerStart: function(element, type, handler) {
var touches = this._pointerTouches;
var cb = function(e) {
// pointer could be mouse or pen
if (!OpenLayers.Event.isTouchEvent(e)) {
return;
}
var alreadyInArray = false;
for (var i=0, ii=touches.length; i<ii; ++i) {
if (touches[i].pointerId == e.pointerId) {
alreadyInArray = true;
break;
}
}
if (!alreadyInArray) {
touches.push(e);
}
e.touches = touches.slice();
handler(e);
};
OpenLayers.Event.observe(element,
this.getTouchModel() === this.TOUCH_MODEL_MSPOINTER ?
'MSPointerDown' : 'pointerdown',
cb);
// the pointerId only needs to be removed from the _pointerTouches array
// when the pointer has left its element
var internalCb = function (e) {
// pointer could be mouse or pen
if (!OpenLayers.Event.isTouchEvent(e)) {
return;
}
var up = false;
for (var i = 0, ii = touches.length; i < ii; ++i) {
if (touches[i].pointerId == e.pointerId) {
if (this.clientWidth != 0 && this.clientHeight != 0) {
if ((Math.ceil(e.clientX) >= this.clientWidth || Math.ceil(e.clientY) >= this.clientHeight)) {
touches.splice(i, 1);
}
}
break;
}
}
};
OpenLayers.Event.observe(element,
this.getTouchModel() === this.TOUCH_MODEL_MSPOINTER ?
'MSPointerOut' : 'pointerout',
internalCb);
},
/**
* Method: addPointerTouchListenerMove
*
* Parameters:
* element - {DOMElement} The DOM element to register the listener on
* type - {String} The event type
* handler - {Function} the handler
*/
addPointerTouchListenerMove: function (element, type, handler) {
var touches = this._pointerTouches;
var cb = function(e) {
// pointer could be mouse or pen
if (!OpenLayers.Event.isTouchEvent(e)) {
return;
}
if (touches.length == 1 && touches[0].pageX == e.pageX &&
touches[0].pageY == e.pageY) {
// don't trigger event when pointer has not moved
return;
}
for (var i=0, ii=touches.length; i<ii; ++i) {
if (touches[i].pointerId == e.pointerId) {
touches[i] = e;
break;
}
}
e.touches = touches.slice();
handler(e);
};
OpenLayers.Event.observe(element,
this.getTouchModel() === this.TOUCH_MODEL_MSPOINTER ?
'MSPointerMove' : 'pointermove',
cb);
},
/**
* Method: addPointerTouchListenerEnd
*
* Parameters:
* element - {DOMElement} The DOM element to register the listener on
* type - {String} The event type
* handler - {Function} the handler
*/
addPointerTouchListenerEnd: function (element, type, handler) {
var touches = this._pointerTouches;
var cb = function(e) {
// pointer could be mouse or pen
if (!OpenLayers.Event.isTouchEvent(e)) {
return;
}
for (var i=0, ii=touches.length; i<ii; ++i) {
if (touches[i].pointerId == e.pointerId) {
touches.splice(i, 1);
break;
}
}
e.touches = touches.slice();
handler(e);
};
OpenLayers.Event.observe(element,
this.getTouchModel() === this.TOUCH_MODEL_MSPOINTER ?
'MSPointerUp' : 'pointerup',
cb);
},
CLASS_NAME: "OpenLayers.Events"
});
/* ======================================================================
OpenLayers/Handler.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/BaseTypes/Class.js
* @requires OpenLayers/Events.js
*/
/**
* Class: OpenLayers.Handler
* Base class to construct a higher-level handler for event sequences. All
* handlers have activate and deactivate methods. In addition, they have
* methods named like browser events. When a handler is activated, any
* additional methods named like a browser event is registered as a
* listener for the corresponding event. When a handler is deactivated,
* those same methods are unregistered as event listeners.
*
* Handlers also typically have a callbacks object with keys named like
* the abstracted events or event sequences that they are in charge of
* handling. The controls that wrap handlers define the methods that
* correspond to these abstract events - so instead of listening for
* individual browser events, they only listen for the abstract events
* defined by the handler.
*
* Handlers are created by controls, which ultimately have the responsibility
* of making changes to the the state of the application. Handlers
* themselves may make temporary changes, but in general are expected to
* return the application in the same state that they found it.
*/
OpenLayers.Handler = OpenLayers.Class({
/**
* Property: id
* {String}
*/
id: null,
/**
* APIProperty: control
* {<OpenLayers.Control>}. The control that initialized this handler. The
* control is assumed to have a valid map property - that map is used
* in the handler's own setMap method.
*/
control: null,
/**
* Property: map
* {<OpenLayers.Map>}
*/
map: null,
/**
* APIProperty: keyMask
* {Integer} Use bitwise operators and one or more of the OpenLayers.Handler
* constants to construct a keyMask. The keyMask is used by
* <checkModifiers>. If the keyMask matches the combination of keys
* down on an event, checkModifiers returns true.
*
* Example:
* (code)
* // handler only responds if the Shift key is down
* handler.keyMask = OpenLayers.Handler.MOD_SHIFT;
*
* // handler only responds if Ctrl-Shift is down
* handler.keyMask = OpenLayers.Handler.MOD_SHIFT |
* OpenLayers.Handler.MOD_CTRL;
* (end)
*/
keyMask: null,
/**
* Property: active
* {Boolean}
*/
active: false,
/**
* Property: evt
* {Event} This property references the last event handled by the handler.
* Note that this property is not part of the stable API. Use of the
* evt property should be restricted to controls in the library
* or other applications that are willing to update with changes to
* the OpenLayers code.
*/
evt: null,
/**
* Property: touch
* {Boolean} Indicates the support of touch events. When touch events are
* started touch will be true and all mouse related listeners will do
* nothing.
*/
touch: false,
/**
* Constructor: OpenLayers.Handler
* Construct a handler.
*
* Parameters:
* control - {<OpenLayers.Control>} The control that initialized this
* handler. The control is assumed to have a valid map property; that
* map is used in the handler's own setMap method. If a map property
* is present in the options argument it will be used instead.
* callbacks - {Object} An object whose properties correspond to abstracted
* events or sequences of browser events. The values for these
* properties are functions defined by the control that get called by
* the handler.
* options - {Object} An optional object whose properties will be set on
* the handler.
*/
initialize: function(control, callbacks, options) {
OpenLayers.Util.extend(this, options);
this.control = control;
this.callbacks = callbacks;
var map = this.map || control.map;
if (map) {
this.setMap(map);
}
this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
},
/**
* Method: setMap
*/
setMap: function (map) {
this.map = map;
},
/**
* Method: checkModifiers
* Check the keyMask on the handler. If no <keyMask> is set, this always
* returns true. If a <keyMask> is set and it matches the combination
* of keys down on an event, this returns true.
*
* Returns:
* {Boolean} The keyMask matches the keys down on an event.
*/
checkModifiers: function (evt) {
if(this.keyMask == null) {
return true;
}
/* calculate the keyboard modifier mask for this event */
var keyModifiers =
(evt.shiftKey ? OpenLayers.Handler.MOD_SHIFT : 0) |
(evt.ctrlKey ? OpenLayers.Handler.MOD_CTRL : 0) |
(evt.altKey ? OpenLayers.Handler.MOD_ALT : 0) |
(evt.metaKey ? OpenLayers.Handler.MOD_META : 0);
/* if it differs from the handler object's key mask,
bail out of the event handler */
return (keyModifiers == this.keyMask);
},
/**
* APIMethod: activate
* Turn on the handler. Returns false if the handler was already active.
*
* Returns:
* {Boolean} The handler was activated.
*/
activate: function() {
if(this.active) {
return false;
}
// register for event handlers defined on this class.
var events = OpenLayers.Events.prototype.BROWSER_EVENTS;
for (var i=0, len=events.length; i<len; i++) {
if (this[events[i]]) {
this.register(events[i], this[events[i]]);
}
}
this.active = true;
return true;
},
/**
* APIMethod: deactivate
* Turn off the handler. Returns false if the handler was already inactive.
*
* Returns:
* {Boolean} The handler was deactivated.
*/
deactivate: function() {
if(!this.active) {
return false;
}
// unregister event handlers defined on this class.
var events = OpenLayers.Events.prototype.BROWSER_EVENTS;
for (var i=0, len=events.length; i<len; i++) {
if (this[events[i]]) {
this.unregister(events[i], this[events[i]]);
}
}
this.touch = false;
this.active = false;
return true;
},
/**
* Method: startTouch
* Start touch events, this method must be called by subclasses in
* "touchstart" method. When touch events are started <touch> will be
* true and all mouse related listeners will do nothing.
*/
startTouch: function() {
if (!this.touch) {
this.touch = true;
var events = [
"mousedown", "mouseup", "mousemove", "click", "dblclick",
"mouseout"
];
for (var i=0, len=events.length; i<len; i++) {
if (this[events[i]]) {
this.unregister(events[i], this[events[i]]);
}
}
}
},
/**
* Method: callback
* Trigger the control's named callback with the given arguments
*
* Parameters:
* name - {String} The key for the callback that is one of the properties
* of the handler's callbacks object.
* args - {Array(*)} An array of arguments (any type) with which to call
* the callback (defined by the control).
*/
callback: function (name, args) {
if (name && this.callbacks[name]) {
this.callbacks[name].apply(this.control, args);
}
},
/**
* Method: register
* register an event on the map
*/
register: function (name, method) {
// TODO: deal with registerPriority in 3.0
this.map.events.registerPriority(name, this, method);
this.map.events.registerPriority(name, this, this.setEvent);
},
/**
* Method: unregister
* unregister an event from the map
*/
unregister: function (name, method) {
this.map.events.unregister(name, this, method);
this.map.events.unregister(name, this, this.setEvent);
},
/**
* Method: setEvent
* With each registered browser event, the handler sets its own evt
* property. This property can be accessed by controls if needed
* to get more information about the event that the handler is
* processing.
*
* This allows modifier keys on the event to be checked (alt, shift, ctrl,
* and meta cannot be checked with the keyboard handler). For a
* control to determine which modifier keys are associated with the
* event that a handler is currently processing, it should access
* (code)handler.evt.altKey || handler.evt.shiftKey ||
* handler.evt.ctrlKey || handler.evt.metaKey(end).
*
* Parameters:
* evt - {Event} The browser event.
*/
setEvent: function(evt) {
this.evt = evt;
return true;
},
/**
* Method: destroy
* Deconstruct the handler.
*/
destroy: function () {
// unregister event listeners
this.deactivate();
// eliminate circular references
this.control = this.map = null;
},
CLASS_NAME: "OpenLayers.Handler"
});
/**
* Constant: OpenLayers.Handler.MOD_NONE
* If set as the <keyMask>, <checkModifiers> returns false if any key is down.
*/
OpenLayers.Handler.MOD_NONE = 0;
/**
* Constant: OpenLayers.Handler.MOD_SHIFT
* If set as the <keyMask>, <checkModifiers> returns false if Shift is down.
*/
OpenLayers.Handler.MOD_SHIFT = 1;
/**
* Constant: OpenLayers.Handler.MOD_CTRL
* If set as the <keyMask>, <checkModifiers> returns false if Ctrl is down.
*/
OpenLayers.Handler.MOD_CTRL = 2;
/**
* Constant: OpenLayers.Handler.MOD_ALT
* If set as the <keyMask>, <checkModifiers> returns false if Alt is down.
*/
OpenLayers.Handler.MOD_ALT = 4;
/**
* Constant: OpenLayers.Handler.MOD_META
* If set as the <keyMask>, <checkModifiers> returns false if Cmd is down.
*/
OpenLayers.Handler.MOD_META = 8;
/* ======================================================================
OpenLayers/Handler/Click.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Handler.js
*/
/**
* Class: OpenLayers.Handler.Click
* A handler for mouse clicks. The intention of this handler is to give
* controls more flexibility with handling clicks. Browsers trigger
* click events twice for a double-click. In addition, the mousedown,
* mousemove, mouseup sequence fires a click event. With this handler,
* controls can decide whether to ignore clicks associated with a double
* click. By setting a <pixelTolerance>, controls can also ignore clicks
* that include a drag. Create a new instance with the
* <OpenLayers.Handler.Click> constructor.
*
* Inherits from:
* - <OpenLayers.Handler>
*/
OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
/**
* APIProperty: delay
* {Number} Number of milliseconds between clicks before the event is
* considered a double-click.
*/
delay: 300,
/**
* APIProperty: single
* {Boolean} Handle single clicks. Default is true. If false, clicks
* will not be reported. If true, single-clicks will be reported.
*/
single: true,
/**
* APIProperty: double
* {Boolean} Handle double-clicks. Default is false.
*/
'double': false,
/**
* APIProperty: pixelTolerance
* {Number} Maximum number of pixels between mouseup and mousedown for an
* event to be considered a click. Default is 0. If set to an
* integer value, clicks with a drag greater than the value will be
* ignored. This property can only be set when the handler is
* constructed.
*/
pixelTolerance: 0,
/**
* APIProperty: dblclickTolerance
* {Number} Maximum distance in pixels between clicks for a sequence of
* events to be considered a double click. Default is 13. If the
* distance between two clicks is greater than this value, a double-
* click will not be fired.
*/
dblclickTolerance: 13,
/**
* APIProperty: stopSingle
* {Boolean} Stop other listeners from being notified of clicks. Default
* is false. If true, any listeners registered before this one for
* click or rightclick events will not be notified.
*/
stopSingle: false,
/**
* APIProperty: stopDouble
* {Boolean} Stop other listeners from being notified of double-clicks.
* Default is false. If true, any click listeners registered before
* this one will not be notified of *any* double-click events.
*
* The one caveat with stopDouble is that given a map with two click
* handlers, one with stopDouble true and the other with stopSingle
* true, the stopSingle handler should be activated last to get
* uniform cross-browser performance. Since IE triggers one click
* with a dblclick and FF triggers two, if a stopSingle handler is
* activated first, all it gets in IE is a single click when the
* second handler stops propagation on the dblclick.
*/
stopDouble: false,
/**
* Property: timerId
* {Number} The id of the timeout waiting to clear the <delayedCall>.
*/
timerId: null,
/**
* Property: down
* {Object} Object that store relevant information about the last
* mousedown or touchstart. Its 'xy' OpenLayers.Pixel property gives
* the average location of the mouse/touch event. Its 'touches'
* property records clientX/clientY of each touches.
*/
down: null,
/**
* Property: last
* {Object} Object that store relevant information about the last
* mousemove or touchmove. Its 'xy' OpenLayers.Pixel property gives
* the average location of the mouse/touch event. Its 'touches'
* property records clientX/clientY of each touches.
*/
last: null,
/**
* Property: first
* {Object} When waiting for double clicks, this object will store
* information about the first click in a two click sequence.
*/
first: null,
/**
* Property: rightclickTimerId
* {Number} The id of the right mouse timeout waiting to clear the
* <delayedEvent>.
*/
rightclickTimerId: null,
/**
* Constructor: OpenLayers.Handler.Click
* Create a new click handler.
*
* Parameters:
* control - {<OpenLayers.Control>} The control that is making use of
* this handler. If a handler is being used without a control, the
* handler's setMap method must be overridden to deal properly with
* the map.
* callbacks - {Object} An object with keys corresponding to callbacks
* that will be called by the handler. The callbacks should
* expect to receive a single argument, the click event.
* Callbacks for 'click' and 'dblclick' are supported.
* options - {Object} Optional object whose properties will be set on the
* handler.
*/
/**
* Method: touchstart
* Handle touchstart.
*
* Returns:
* {Boolean} Continue propagating this event.
*/
touchstart: function(evt) {
this.startTouch();
this.down = this.getEventInfo(evt);
this.last = this.getEventInfo(evt);
return true;
},
/**
* Method: touchmove
* Store position of last move, because touchend event can have
* an empty "touches" property.
*
* Returns:
* {Boolean} Continue propagating this event.
*/
touchmove: function(evt) {
this.last = this.getEventInfo(evt);
return true;
},
/**
* Method: touchend
* Correctly set event xy property, and add lastTouches to have
* touches property from last touchstart or touchmove
*
* Returns:
* {Boolean} Continue propagating this event.
*/
touchend: function(evt) {
// touchstart may not have been allowed to propagate
if (this.down) {
evt.xy = this.last.xy;
evt.lastTouches = this.last.touches;
this.handleSingle(evt);
this.down = null;
}
return true;
},
/**
* Method: mousedown
* Handle mousedown.
*
* Returns:
* {Boolean} Continue propagating this event.
*/
mousedown: function(evt) {
this.down = this.getEventInfo(evt);
this.last = this.getEventInfo(evt);
return true;
},
/**
* Method: mouseup
* Handle mouseup. Installed to support collection of right mouse events.
*
* Returns:
* {Boolean} Continue propagating this event.
*/
mouseup: function (evt) {
var propagate = true;
// Collect right mouse clicks from the mouseup
// IE - ignores the second right click in mousedown so using
// mouseup instead
if (this.checkModifiers(evt) && this.control.handleRightClicks &&
OpenLayers.Event.isRightClick(evt)) {
propagate = this.rightclick(evt);
}
return propagate;
},
/**
* Method: rightclick
* Handle rightclick. For a dblrightclick, we get two clicks so we need
* to always register for dblrightclick to properly handle single
* clicks.
*
* Returns:
* {Boolean} Continue propagating this event.
*/
rightclick: function(evt) {
if(this.passesTolerance(evt)) {
if(this.rightclickTimerId != null) {
//Second click received before timeout this must be
// a double click
this.clearTimer();
this.callback('dblrightclick', [evt]);
return !this.stopDouble;
} else {
//Set the rightclickTimerId, send evt only if double is
// true else trigger single
var clickEvent = this['double'] ?
OpenLayers.Util.extend({}, evt) :
this.callback('rightclick', [evt]);
var delayedRightCall = OpenLayers.Function.bind(
this.delayedRightCall,
this,
clickEvent
);
this.rightclickTimerId = window.setTimeout(
delayedRightCall, this.delay
);
}
}
return !this.stopSingle;
},
/**
* Method: delayedRightCall
* Sets <rightclickTimerId> to null. And optionally triggers the
* rightclick callback if evt is set.
*/
delayedRightCall: function(evt) {
this.rightclickTimerId = null;
if (evt) {
this.callback('rightclick', [evt]);
}
},
/**
* Method: click
* Handle click events from the browser. This is registered as a listener
* for click events and should not be called from other events in this
* handler.
*
* Returns:
* {Boolean} Continue propagating this event.
*/
click: function(evt) {
if (!this.last) {
this.last = this.getEventInfo(evt);
}
this.handleSingle(evt);
return !this.stopSingle;
},
/**
* Method: dblclick
* Handle dblclick. For a dblclick, we get two clicks in some browsers
* (FF) and one in others (IE). So we need to always register for
* dblclick to properly handle single clicks. This method is registered
* as a listener for the dblclick browser event. It should *not* be
* called by other methods in this handler.
*
* Returns:
* {Boolean} Continue propagating this event.
*/
dblclick: function(evt) {
this.handleDouble(evt);
return !this.stopDouble;
},
/**
* Method: handleDouble
* Handle double-click sequence.
*/
handleDouble: function(evt) {
if (this.passesDblclickTolerance(evt)) {
if (this["double"]) {
this.callback("dblclick", [evt]);
}
// to prevent a dblclick from firing the click callback in IE
this.clearTimer();
}
},
/**
* Method: handleSingle
* Handle single click sequence.
*/
handleSingle: function(evt) {
if (this.passesTolerance(evt)) {
if (this.timerId != null) {
// already received a click
if (this.last.touches && this.last.touches.length === 1) {
// touch device, no dblclick event - this may be a double
if (this["double"]) {
// on Android don't let the browser zoom on the page
OpenLayers.Event.preventDefault(evt);
}
this.handleDouble(evt);
}
// if we're not in a touch environment we clear the click timer
// if we've got a second touch, we'll get two touchend events
if (!this.last.touches || this.last.touches.length !== 2) {
this.clearTimer();
}
} else {
// remember the first click info so we can compare to the second
this.first = this.getEventInfo(evt);
// set the timer, send evt only if single is true
//use a clone of the event object because it will no longer
//be a valid event object in IE in the timer callback
var clickEvent = this.single ?
OpenLayers.Util.extend({}, evt) : null;
this.queuePotentialClick(clickEvent);
}
}
},
/**
* Method: queuePotentialClick
* This method is separated out largely to make testing easier (so we
* don't have to override window.setTimeout)
*/
queuePotentialClick: function(evt) {
this.timerId = window.setTimeout(
OpenLayers.Function.bind(this.delayedCall, this, evt),
this.delay
);
},
/**
* Method: passesTolerance
* Determine whether the event is within the optional pixel tolerance. Note
* that the pixel tolerance check only works if mousedown events get to
* the listeners registered here. If they are stopped by other elements,
* the <pixelTolerance> will have no effect here (this method will always
* return true).
*
* Returns:
* {Boolean} The click is within the pixel tolerance (if specified).
*/
passesTolerance: function(evt) {
var passes = true;
if (this.pixelTolerance != null && this.down && this.down.xy) {
passes = this.pixelTolerance >= this.down.xy.distanceTo(evt.xy);
// for touch environments, we also enforce that all touches
// start and end within the given tolerance to be considered a click
if (passes && this.touch &&
this.down.touches.length === this.last.touches.length) {
// the touchend event doesn't come with touches, so we check
// down and last
for (var i=0, ii=this.down.touches.length; i<ii; ++i) {
if (this.getTouchDistance(
this.down.touches[i],
this.last.touches[i]
) > this.pixelTolerance) {
passes = false;
break;
}
}
}
}
return passes;
},
/**
* Method: getTouchDistance
*
* Returns:
* {Boolean} The pixel displacement between two touches.
*/
getTouchDistance: function(from, to) {
return Math.sqrt(
Math.pow(from.clientX - to.clientX, 2) +
Math.pow(from.clientY - to.clientY, 2)
);
},
/**
* Method: passesDblclickTolerance
* Determine whether the event is within the optional double-cick pixel
* tolerance.
*
* Returns:
* {Boolean} The click is within the double-click pixel tolerance.
*/
passesDblclickTolerance: function(evt) {
var passes = true;
if (this.down && this.first) {
passes = this.down.xy.distanceTo(this.first.xy) <= this.dblclickTolerance;
}
return passes;
},
/**
* Method: clearTimer
* Clear the timer and set <timerId> to null.
*/
clearTimer: function() {
if (this.timerId != null) {
window.clearTimeout(this.timerId);
this.timerId = null;
}
if (this.rightclickTimerId != null) {
window.clearTimeout(this.rightclickTimerId);
this.rightclickTimerId = null;
}
},
/**
* Method: delayedCall
* Sets <timerId> to null. And optionally triggers the click callback if
* evt is set.
*/
delayedCall: function(evt) {
this.timerId = null;
if (evt) {
this.callback("click", [evt]);
}
},
/**
* Method: getEventInfo
* This method allows us to store event information without storing the
* actual event. In touch devices (at least), the same event is
* modified between touchstart, touchmove, and touchend.
*
* Returns:
* {Object} An object with event related info.
*/
getEventInfo: function(evt) {
var touches;
if (evt.touches) {
var len = evt.touches.length;
touches = new Array(len);
var touch;
for (var i=0; i<len; i++) {
touch = evt.touches[i];
touches[i] = {
clientX: touch.olClientX,
clientY: touch.olClientY
};
}
}
return {
xy: evt.xy,
touches: touches
};
},
/**
* APIMethod: deactivate
* Deactivate the handler.
*
* Returns:
* {Boolean} The handler was successfully deactivated.
*/
deactivate: function() {
var deactivated = false;
if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
this.clearTimer();
this.down = null;
this.first = null;
this.last = null;
deactivated = true;
}
return deactivated;
},
CLASS_NAME: "OpenLayers.Handler.Click"
});
/* ======================================================================
OpenLayers/Popup.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published 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 <OpenLayers.Marker>. Popup's don't require their own
* layer and are added the the map using the <OpenLayers.Map.addPopup>
* 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
* {<OpenLayers.Events>} custom event manager
*/
events: null,
/** Property: id
* {String} the unique identifier assigned to this popup.
*/
id: "",
/**
* Property: lonlat
* {<OpenLayers.LonLat>} the position of this popup on the map
*/
lonlat: null,
/**
* Property: div
* {DOMElement} the div that contains this popup.
*/
div: null,
/**
* Property: contentSize
* {<OpenLayers.Size>} the width and height of the content.
*/
contentSize: null,
/**
* Property: size
* {<OpenLayers.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
* {<OpenLayers.Size>} Minimum size allowed for the popup's contents.
*/
minSize: null,
/**
* APIProperty: maxSize
* {<OpenLayers.Size>} 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 <OpenLayers.Bounds>} 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 <OpenLayers.Bounds> 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
* {<OpenLayers.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 - {<OpenLayers.LonLat>} The position on the map the popup will
* be shown.
* contentSize - {<OpenLayers.Size>} 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 - {<OpenLayers.Pixel>} 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 - {<OpenLayers.Pixel>} 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 - {<OpenLayers.Size>} 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 = "<div class='" + this.contentDisplayClass+ "'>" +
this.contentDiv.innerHTML +
"</div>";
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 - {<OpenLayers.Size>} Desired size to make the popup.
*
* Returns:
* {<OpenLayers.Size>} 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:
* {<OpenLayers.Bounds>}
*/
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-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published 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>
*/
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' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>).
*/
anchor: null,
/**
* Constructor: OpenLayers.Popup.Anchored
*
* Parameters:
* id - {String}
* lonlat - {<OpenLayers.LonLat>}
* contentSize - {<OpenLayers.Size>}
* contentHTML - {String}
* anchor - {Object} Object which must expose a 'size' <OpenLayers.Size>
* and 'offset' <OpenLayers.Pixel> (generally an <OpenLayers.Icon>).
* 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 - {<OpenLayers.Pixel>}
*/
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 - {<OpenLayers.Size>} 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 - {<OpenLayers.Pixel>}
*
* 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 - {<OpenLayers.Pixel>}
*
* Returns:
* {<OpenLayers.Pixel>} 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-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published 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.Anchored>
*/
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
* {<OpenLayers.Size>} 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 - {<OpenLayers.LonLat>}
* contentSize - {<OpenLayers.Size>}
* contentHTML - {String}
* anchor - {Object} Object to which we'll anchor the popup. Must expose
* a 'size' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>)
* (Note that this is generally an <OpenLayers.Icon>).
* 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 - {<OpenLayers.Size>} 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 - {<OpenLayers.Pixel>}
*
* Returns:
* {<OpenLayers.Pixel>} 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/Popup/FramedCloud.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published 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.Framed>
*/
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
* {<OpenLayers.Size>}
*/
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
* {<OpenLayers.Size>}
*/
minSize: new OpenLayers.Size(105, 10),
/**
* APIProperty: maxSize
* {<OpenLayers.Size>}
*/
maxSize: new OpenLayers.Size(1200, 660),
/**
* Constructor: OpenLayers.Popup.FramedCloud
*
* Parameters:
* id - {String}
* lonlat - {<OpenLayers.LonLat>}
* contentSize - {<OpenLayers.Size>}
* contentHTML - {String}
* anchor - {Object} Object to which we'll anchor the popup. Must expose
* a 'size' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>)
* (Note that this is generally an <OpenLayers.Icon>).
* 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/Renderer.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published 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
* caching system.
*
*/
OpenLayers.Renderer = OpenLayers.Class({
/**
* Property: container
* {DOMElement}
*/
container: null,
/**
* Property: root
* {DOMElement}
*/
root: null,
/**
* Property: extent
* {<OpenLayers.Bounds>}
*/
extent: null,
/**
* Property: locked
* {Boolean} If the renderer is currently in a state where many things
* are changing, the 'locked' property is set to true. This means
* that renderers can expect at least one more drawFeature event to be
* called with the 'locked' property set to 'true': In some renderers,
* this might make sense to use as a 'only update local information'
* flag.
*/
locked: false,
/**
* Property: size
* {<OpenLayers.Size>}
*/
size: null,
/**
* Property: resolution
* {Float} cache of current map resolution
*/
resolution: null,
/**
* Property: map
* {<OpenLayers.Map>} Reference to the map -- this is set in Vector's setMap()
*/
map: null,
/**
* Property: featureDx
* {Number} Feature offset in x direction. Will be calculated for and
* applied to the current feature while rendering (see
* <calculateFeatureDx>).
*/
featureDx: 0,
/**
* Constructor: OpenLayers.Renderer
*
* Parameters:
* containerID - {<String>}
* options - {Object} options for this renderer. See sublcasses for
* supported options.
*/
initialize: function(containerID, options) {
this.container = OpenLayers.Util.getElement(containerID);
OpenLayers.Util.extend(this, options);
},
/**
* APIMethod: destroy
*/
destroy: function() {
this.container = null;
this.extent = null;
this.size = null;
this.resolution = null;
this.map = null;
},
/**
* APIMethod: supported
* This should be overridden by specific subclasses
*
* Returns:
* {Boolean} Whether or not the browser supports the renderer class
*/
supported: function() {
return false;
},
/**
* Method: setExtent
* Set the visible part of the layer.
*
* Resolution has probably changed, so we nullify the resolution
* cache (this.resolution) -- this way it will be re-computed when
* next it is needed.
* We nullify the resolution cache (this.resolution) if resolutionChanged
* is set to true - this way it will be re-computed on the next
* getResolution() request.
*
* Parameters:
* extent - {<OpenLayers.Bounds>}
* resolutionChanged - {Boolean}
*
* Returns:
* {Boolean} true to notify the layer that the new extent does not exceed
* the coordinate range, and the features will not need to be redrawn.
* False otherwise.
*/
setExtent: function(extent, resolutionChanged) {
this.extent = extent.clone();
if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
var ratio = extent.getWidth() / this.map.getExtent().getWidth(),
extent = extent.scale(1 / ratio);
this.extent = extent.wrapDateLine(this.map.getMaxExtent()).scale(ratio);
}
if (resolutionChanged) {
this.resolution = null;
}
return true;
},
/**
* Method: setSize
* Sets the size of the drawing surface.
*
* Resolution has probably changed, so we nullify the resolution
* cache (this.resolution) -- this way it will be re-computed when
* next it is needed.
*
* Parameters:
* size - {<OpenLayers.Size>}
*/
setSize: function(size) {
this.size = size.clone();
this.resolution = null;
},
/**
* Method: getResolution
* Uses cached copy of resolution if available to minimize computing
*
* Returns:
* {Float} The current map's resolution
*/
getResolution: function() {
this.resolution = this.resolution || this.map.getResolution();
return this.resolution;
},
/**
* Method: drawFeature
* Draw the feature. The optional style argument can be used
* to override the feature's own style. This method should only
* be called from layer.drawFeature().
*
* Parameters:
* feature - {<OpenLayers.Feature.Vector>}
* style - {<Object>}
*
* Returns:
* {Boolean} true if the feature has been drawn completely, false if not,
* undefined if the feature had no geometry
*/
drawFeature: function(feature, style) {
if(style == null) {
style = feature.style;
}
if (feature.geometry) {
var bounds = feature.geometry.getBounds();
if(bounds) {
var worldBounds;
if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
worldBounds = this.map.getMaxExtent();
}
if (!bounds.intersectsBounds(this.extent, {worldBounds: worldBounds})) {
style = {display: "none"};
} else {
this.calculateFeatureDx(bounds, worldBounds);
}
var rendered = this.drawGeometry(feature.geometry, style, feature.id);
if(style.display != "none" && style.label && rendered !== false) {
var location = feature.geometry.getCentroid();
if(style.labelXOffset || style.labelYOffset) {
var xOffset = isNaN(style.labelXOffset) ? 0 : style.labelXOffset;
var yOffset = isNaN(style.labelYOffset) ? 0 : style.labelYOffset;
var res = this.getResolution();
location.move(xOffset*res, yOffset*res);
}
this.drawText(feature.id, style, location);
} else {
this.removeText(feature.id);
}
return rendered;
}
}
},
/**
* Method: calculateFeatureDx
* {Number} Calculates the feature offset in x direction. Looking at the
* center of the feature bounds and the renderer extent, we calculate how
* many world widths the two are away from each other. This distance is
* used to shift the feature as close as possible to the center of the
* current enderer extent, which ensures that the feature is visible in the
* current viewport.
*
* Parameters:
* bounds - {<OpenLayers.Bounds>} Bounds of the feature
* worldBounds - {<OpenLayers.Bounds>} Bounds of the world
*/
calculateFeatureDx: function(bounds, worldBounds) {
this.featureDx = 0;
if (worldBounds) {
var worldWidth = worldBounds.getWidth(),
rendererCenterX = (this.extent.left + this.extent.right) / 2,
featureCenterX = (bounds.left + bounds.right) / 2,
worldsAway = Math.round((featureCenterX - rendererCenterX) / worldWidth);
this.featureDx = worldsAway * worldWidth;
}
},
/**
* Method: drawGeometry
*
* Draw a geometry. This should only be called from the renderer itself.
* Use layer.drawFeature() from outside the renderer.
* virtual function
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
* style - {Object}
* featureId - {<String>}
*/
drawGeometry: function(geometry, style, featureId) {},
/**
* Method: drawText
* Function for drawing text labels.
* This method is only called by the renderer itself.
*
* Parameters:
* featureId - {String}
* style -
* location - {<OpenLayers.Geometry.Point>}
*/
drawText: function(featureId, style, location) {},
/**
* Method: removeText
* Function for removing text labels.
* This method is only called by the renderer itself.
*
* Parameters:
* featureId - {String}
*/
removeText: function(featureId) {},
/**
* Method: clear
* Clear all vectors from the renderer.
* virtual function.
*/
clear: function() {},
/**
* Method: getFeatureIdFromEvent
* Returns a feature id from an event on the renderer.
* How this happens is specific to the renderer. This should be
* called from layer.getFeatureFromEvent().
* Virtual function.
*
* Parameters:
* evt - {<OpenLayers.Event>}
*
* Returns:
* {String} A feature id or undefined.
*/
getFeatureIdFromEvent: function(evt) {},
/**
* Method: eraseFeatures
* This is called by the layer to erase features
*
* Parameters:
* features - {Array(<OpenLayers.Feature.Vector>)}
*/
eraseFeatures: function(features) {
if(!(OpenLayers.Util.isArray(features))) {
features = [features];
}
for(var i=0, len=features.length; i<len; ++i) {
var feature = features[i];
this.eraseGeometry(feature.geometry, feature.id);
this.removeText(feature.id);
}
},
/**
* Method: eraseGeometry
* Remove a geometry from the renderer (by id).
* virtual function.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
* featureId - {String}
*/
eraseGeometry: function(geometry, featureId) {},
/**
* Method: moveRoot
* moves this renderer's root to a (different) renderer.
* To be implemented by subclasses that require a common renderer root for
* feature selection.
*
* Parameters:
* renderer - {<OpenLayers.Renderer>} target renderer for the moved root
*/
moveRoot: function(renderer) {},
/**
* Method: getRenderLayerId
* Gets the layer that this renderer's output appears on. If moveRoot was
* used, this will be different from the id of the layer containing the
* features rendered by this renderer.
*
* Returns:
* {String} the id of the output layer.
*/
getRenderLayerId: function() {
return this.container.id;
},
/**
* Method: applyDefaultSymbolizer
*
* Parameters:
* symbolizer - {Object}
*
* Returns:
* {Object}
*/
applyDefaultSymbolizer: function(symbolizer) {
var result = OpenLayers.Util.extend({},
OpenLayers.Renderer.defaultSymbolizer);
if(symbolizer.stroke === false) {
delete result.strokeWidth;
delete result.strokeColor;
}
if(symbolizer.fill === false) {
delete result.fillColor;
}
OpenLayers.Util.extend(result, symbolizer);
return result;
},
CLASS_NAME: "OpenLayers.Renderer"
});
/**
* Constant: OpenLayers.Renderer.defaultSymbolizer
* {Object} Properties from this symbolizer will be applied to symbolizers
* with missing properties. This can also be used to set a global
* symbolizer default in OpenLayers. To be SLD 1.x compliant, add the
* following code before rendering any vector features:
* (code)
* OpenLayers.Renderer.defaultSymbolizer = {
* fillColor: "#808080",
* fillOpacity: 1,
* strokeColor: "#000000",
* strokeOpacity: 1,
* strokeWidth: 1,
* pointRadius: 3,
* graphicName: "square"
* };
* (end)
*/
OpenLayers.Renderer.defaultSymbolizer = {
fillColor: "#000000",
strokeColor: "#000000",
strokeWidth: 2,
fillOpacity: 1,
strokeOpacity: 1,
pointRadius: 0,
labelAlign: 'cm'
};
/**
* Constant: OpenLayers.Renderer.symbol
* Coordinate arrays for well known (named) symbols.
*/
OpenLayers.Renderer.symbol = {
"star": [350,75, 379,161, 469,161, 397,215, 423,301, 350,250, 277,301,
303,215, 231,161, 321,161, 350,75],
"cross": [4,0, 6,0, 6,4, 10,4, 10,6, 6,6, 6,10, 4,10, 4,6, 0,6, 0,4, 4,4,
4,0],
"x": [0,0, 25,0, 50,35, 75,0, 100,0, 65,50, 100,100, 75,100, 50,65, 25,100, 0,100, 35,50, 0,0],
"square": [0,0, 0,1, 1,1, 1,0, 0,0],
"triangle": [0,10, 10,10, 5,0, 0,10]
};
/* ======================================================================
OpenLayers/Renderer/Canvas.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Renderer.js
*/
/**
* Class: OpenLayers.Renderer.Canvas
* A renderer based on the 2D 'canvas' drawing element.
*
* Inherits:
* - <OpenLayers.Renderer>
*/
OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
/**
* APIProperty: hitDetection
* {Boolean} Allow for hit detection of features. Default is true.
*/
hitDetection: true,
/**
* Property: hitOverflow
* {Number} The method for converting feature identifiers to color values
* supports 16777215 sequential values. Two features cannot be
* predictably detected if their identifiers differ by more than this
* value. The hitOverflow allows for bigger numbers (but the
* difference in values is still limited).
*/
hitOverflow: 0,
/**
* Property: canvas
* {Canvas} The canvas context object.
*/
canvas: null,
/**
* Property: features
* {Object} Internal object of feature/style pairs for use in redrawing the layer.
*/
features: null,
/**
* Property: pendingRedraw
* {Boolean} The renderer needs a redraw call to render features added while
* the renderer was locked.
*/
pendingRedraw: false,
/**
* Property: cachedSymbolBounds
* {Object} Internal cache of calculated symbol extents.
*/
cachedSymbolBounds: {},
/**
* Constructor: OpenLayers.Renderer.Canvas
*
* Parameters:
* containerID - {<String>}
* options - {Object} Optional properties to be set on the renderer.
*/
initialize: function(containerID, options) {
OpenLayers.Renderer.prototype.initialize.apply(this, arguments);
this.root = document.createElement("canvas");
this.container.appendChild(this.root);
this.canvas = this.root.getContext("2d");
this._clearRectId = OpenLayers.Util.createUniqueID();
this.features = {};
if (this.hitDetection) {
this.hitCanvas = document.createElement("canvas");
this.hitContext = this.hitCanvas.getContext("2d");
}
},
/**
* Method: setExtent
* Set the visible part of the layer.
*
* Parameters:
* extent - {<OpenLayers.Bounds>}
* resolutionChanged - {Boolean}
*
* Returns:
* {Boolean} true to notify the layer that the new extent does not exceed
* the coordinate range, and the features will not need to be redrawn.
* False otherwise.
*/
setExtent: function() {
OpenLayers.Renderer.prototype.setExtent.apply(this, arguments);
// always redraw features
return false;
},
/**
* Method: eraseGeometry
* Erase a geometry from the renderer. Because the Canvas renderer has
* 'memory' of the features that it has drawn, we have to remove the
* feature so it doesn't redraw.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
* featureId - {String}
*/
eraseGeometry: function(geometry, featureId) {
this.eraseFeatures(this.features[featureId][0]);
},
/**
* APIMethod: supported
*
* Returns:
* {Boolean} Whether or not the browser supports the renderer class
*/
supported: function() {
return OpenLayers.CANVAS_SUPPORTED;
},
/**
* Method: setSize
* Sets the size of the drawing surface.
*
* Once the size is updated, redraw the canvas.
*
* Parameters:
* size - {<OpenLayers.Size>}
*/
setSize: function(size) {
this.size = size.clone();
var root = this.root;
root.style.width = size.w + "px";
root.style.height = size.h + "px";
root.width = size.w;
root.height = size.h;
this.resolution = null;
if (this.hitDetection) {
var hitCanvas = this.hitCanvas;
hitCanvas.style.width = size.w + "px";
hitCanvas.style.height = size.h + "px";
hitCanvas.width = size.w;
hitCanvas.height = size.h;
}
},
/**
* Method: drawFeature
* Draw the feature. Stores the feature in the features list,
* then redraws the layer.
*
* Parameters:
* feature - {<OpenLayers.Feature.Vector>}
* style - {<Object>}
*
* Returns:
* {Boolean} The feature has been drawn completely. If the feature has no
* geometry, undefined will be returned. If the feature is not rendered
* for other reasons, false will be returned.
*/
drawFeature: function(feature, style) {
var rendered;
if (feature.geometry) {
style = this.applyDefaultSymbolizer(style || feature.style);
// don't render if display none or feature outside extent
var bounds = feature.geometry.getBounds();
var worldBounds;
if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
worldBounds = this.map.getMaxExtent();
}
var intersects = bounds && bounds.intersectsBounds(this.extent, {worldBounds: worldBounds});
rendered = (style.display !== "none") && !!bounds && intersects;
if (rendered) {
// keep track of what we have rendered for redraw
this.features[feature.id] = [feature, style];
}
else {
// remove from features tracked for redraw
delete(this.features[feature.id]);
}
this.pendingRedraw = true;
}
if (this.pendingRedraw && !this.locked) {
this.redraw();
this.pendingRedraw = false;
}
return rendered;
},
/**
* Method: drawGeometry
* Used when looping (in redraw) over the features; draws
* the canvas.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
* style - {Object}
*/
drawGeometry: function(geometry, style, featureId) {
var className = geometry.CLASS_NAME;
if ((className == "OpenLayers.Geometry.Collection") ||
(className == "OpenLayers.Geometry.MultiPoint") ||
(className == "OpenLayers.Geometry.MultiLineString") ||
(className == "OpenLayers.Geometry.MultiPolygon")) {
var worldBounds = (this.map.baseLayer && this.map.baseLayer.wrapDateLine) && this.map.getMaxExtent();
for (var i = 0; i < geometry.components.length; i++) {
this.calculateFeatureDx(geometry.components[i].getBounds(), worldBounds);
this.drawGeometry(geometry.components[i], style, featureId);
}
return;
}
switch (geometry.CLASS_NAME) {
case "OpenLayers.Geometry.Point":
this.drawPoint(geometry, style, featureId);
break;
case "OpenLayers.Geometry.LineString":
this.drawLineString(geometry, style, featureId);
break;
case "OpenLayers.Geometry.LinearRing":
this.drawLinearRing(geometry, style, featureId);
break;
case "OpenLayers.Geometry.Polygon":
this.drawPolygon(geometry, style, featureId);
break;
default:
break;
}
},
/**
* Method: drawExternalGraphic
* Called to draw External graphics.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
* style - {Object}
* featureId - {String}
*/
drawExternalGraphic: function(geometry, style, featureId) {
var img = new Image();
var title = style.title || style.graphicTitle;
if (title) {
img.title = title;
}
var width = style.graphicWidth || style.graphicHeight;
var height = style.graphicHeight || style.graphicWidth;
width = width ? width : style.pointRadius * 2;
height = height ? height : style.pointRadius * 2;
var xOffset = (style.graphicXOffset != undefined) ?
style.graphicXOffset : -(0.5 * width);
var yOffset = (style.graphicYOffset != undefined) ?
style.graphicYOffset : -(0.5 * height);
var _clearRectId = this._clearRectId;
var opacity = style.graphicOpacity || style.fillOpacity;
var onLoad = function() {
if(!this.features[featureId] ||
_clearRectId !== this._clearRectId) {
return;
}
var pt = this.getLocalXY(geometry);
var p0 = pt[0];
var p1 = pt[1];
if(!isNaN(p0) && !isNaN(p1)) {
var x = (p0 + xOffset) | 0;
var y = (p1 + yOffset) | 0;
var canvas = this.canvas;
canvas.globalAlpha = opacity;
var factor = OpenLayers.Renderer.Canvas.drawImageScaleFactor ||
(OpenLayers.Renderer.Canvas.drawImageScaleFactor =
/android 2.1/.test(navigator.userAgent.toLowerCase()) ?
// 320 is the screen width of the G1 phone, for
// which drawImage works out of the box.
320 / window.screen.width : 1
);
canvas.drawImage(
img, x*factor, y*factor, width*factor, height*factor
);
if (this.hitDetection) {
this.setHitContextStyle("fill", featureId);
this.hitContext.fillRect(x, y, width, height);
}
}
};
img.onload = OpenLayers.Function.bind(onLoad, this);
img.src = style.externalGraphic;
if (img.complete) {
img.onload();
img.onload = null;
}
},
/**
* Method: drawNamedSymbol
* Called to draw Well Known Graphic Symbol Name.
* This method is only called by the renderer itself.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
* style - {Object}
* featureId - {String}
*/
drawNamedSymbol: function(geometry, style, featureId) {
var x, y, cx, cy, i, symbolBounds, scaling, angle;
var unscaledStrokeWidth;
var deg2rad = Math.PI / 180.0;
var symbol = OpenLayers.Renderer.symbol[style.graphicName];
if (!symbol) {
throw new Error(style.graphicName + ' is not a valid symbol name');
}
if (!symbol.length || symbol.length < 2) return;
var pt = this.getLocalXY(geometry);
var p0 = pt[0];
var p1 = pt[1];
if (isNaN(p0) || isNaN(p1)) return;
// Use rounded line caps
this.canvas.lineCap = "round";
this.canvas.lineJoin = "round";
if (this.hitDetection) {
this.hitContext.lineCap = "round";
this.hitContext.lineJoin = "round";
}
// Scale and rotate symbols, using precalculated bounds whenever possible.
if (style.graphicName in this.cachedSymbolBounds) {
symbolBounds = this.cachedSymbolBounds[style.graphicName];
} else {
symbolBounds = new OpenLayers.Bounds();
for(i = 0; i < symbol.length; i+=2) {
symbolBounds.extend(new OpenLayers.LonLat(symbol[i], symbol[i+1]));
}
this.cachedSymbolBounds[style.graphicName] = symbolBounds;
}
// Push symbol scaling, translation and rotation onto the transformation stack in reverse order.
// Don't forget to apply all canvas transformations to the hitContext canvas as well(!)
this.canvas.save();
if (this.hitDetection) { this.hitContext.save(); }
// Step 3: place symbol at the desired location
this.canvas.translate(p0,p1);
if (this.hitDetection) { this.hitContext.translate(p0,p1); }
// Step 2a. rotate the symbol if necessary
angle = deg2rad * style.rotation; // will be NaN when style.rotation is undefined.
if (!isNaN(angle)) {
this.canvas.rotate(angle);
if (this.hitDetection) { this.hitContext.rotate(angle); }
}
// // Step 2: scale symbol such that pointRadius equals half the maximum symbol dimension.
scaling = 2.0 * style.pointRadius / Math.max(symbolBounds.getWidth(), symbolBounds.getHeight());
this.canvas.scale(scaling,scaling);
if (this.hitDetection) { this.hitContext.scale(scaling,scaling); }
// Step 1: center the symbol at the origin
cx = symbolBounds.getCenterLonLat().lon;
cy = symbolBounds.getCenterLonLat().lat;
this.canvas.translate(-cx,-cy);
if (this.hitDetection) { this.hitContext.translate(-cx,-cy); }
// Don't forget to scale stroke widths, because they are affected by canvas scale transformations as well(!)
// Alternative: scale symbol coordinates manually, so stroke width scaling is not needed anymore.
unscaledStrokeWidth = style.strokeWidth;
style.strokeWidth = unscaledStrokeWidth / scaling;
if (style.fill !== false) {
this.setCanvasStyle("fill", style);
this.canvas.beginPath();
for (i=0; i<symbol.length; i=i+2) {
x = symbol[i];
y = symbol[i+1];
if (i == 0) this.canvas.moveTo(x,y);
this.canvas.lineTo(x,y);
}
this.canvas.closePath();
this.canvas.fill();
if (this.hitDetection) {
this.setHitContextStyle("fill", featureId, style);
this.hitContext.beginPath();
for (i=0; i<symbol.length; i=i+2) {
x = symbol[i];
y = symbol[i+1];
if (i == 0) this.canvas.moveTo(x,y);
this.hitContext.lineTo(x,y);
}
this.hitContext.closePath();
this.hitContext.fill();
}
}
if (style.stroke !== false) {
this.setCanvasStyle("stroke", style);
this.canvas.beginPath();
for (i=0; i<symbol.length; i=i+2) {
x = symbol[i];
y = symbol[i+1];
if (i == 0) this.canvas.moveTo(x,y);
this.canvas.lineTo(x,y);
}
this.canvas.closePath();
this.canvas.stroke();
if (this.hitDetection) {
this.setHitContextStyle("stroke", featureId, style, scaling);
this.hitContext.beginPath();
for (i=0; i<symbol.length; i=i+2) {
x = symbol[i];
y = symbol[i+1];
if (i == 0) this.hitContext.moveTo(x,y);
this.hitContext.lineTo(x,y);
}
this.hitContext.closePath();
this.hitContext.stroke();
}
}
style.strokeWidth = unscaledStrokeWidth;
this.canvas.restore();
if (this.hitDetection) { this.hitContext.restore(); }
this.setCanvasStyle("reset");
},
/**
* Method: setCanvasStyle
* Prepare the canvas for drawing by setting various global settings.
*
* Parameters:
* type - {String} one of 'stroke', 'fill', or 'reset'
* style - {Object} Symbolizer hash
*/
setCanvasStyle: function(type, style) {
if (type === "fill") {
this.canvas.globalAlpha = style['fillOpacity'];
this.canvas.fillStyle = style['fillColor'];
} else if (type === "stroke") {
this.canvas.globalAlpha = style['strokeOpacity'];
this.canvas.strokeStyle = style['strokeColor'];
this.canvas.lineWidth = style['strokeWidth'];
} else {
this.canvas.globalAlpha = 0;
this.canvas.lineWidth = 1;
}
},
/**
* Method: featureIdToHex
* Convert a feature ID string into an RGB hex string.
*
* Parameters:
* featureId - {String} Feature id
*
* Returns:
* {String} RGB hex string.
*/
featureIdToHex: function(featureId) {
var id = Number(featureId.split("_").pop()) + 1; // zero for no feature
if (id >= 16777216) {
this.hitOverflow = id - 16777215;
id = id % 16777216 + 1;
}
var hex = "000000" + id.toString(16);
var len = hex.length;
hex = "#" + hex.substring(len-6, len);
return hex;
},
/**
* Method: setHitContextStyle
* Prepare the hit canvas for drawing by setting various global settings.
*
* Parameters:
* type - {String} one of 'stroke', 'fill', or 'reset'
* featureId - {String} The feature id.
* symbolizer - {<OpenLayers.Symbolizer>} The symbolizer.
*/
setHitContextStyle: function(type, featureId, symbolizer, strokeScaling) {
var hex = this.featureIdToHex(featureId);
if (type == "fill") {
this.hitContext.globalAlpha = 1.0;
this.hitContext.fillStyle = hex;
} else if (type == "stroke") {
this.hitContext.globalAlpha = 1.0;
this.hitContext.strokeStyle = hex;
// bump up stroke width to deal with antialiasing. If strokeScaling is defined, we're rendering a symbol
// on a transformed canvas, so the antialias width bump has to scale as well.
if (typeof strokeScaling === "undefined") {
this.hitContext.lineWidth = symbolizer.strokeWidth + 2;
} else {
if (!isNaN(strokeScaling)) { this.hitContext.lineWidth = symbolizer.strokeWidth + 2.0 / strokeScaling; }
}
} else {
this.hitContext.globalAlpha = 0;
this.hitContext.lineWidth = 1;
}
},
/**
* Method: drawPoint
* This method is only called by the renderer itself.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
* style - {Object}
* featureId - {String}
*/
drawPoint: function(geometry, style, featureId) {
if(style.graphic !== false) {
if(style.externalGraphic) {
this.drawExternalGraphic(geometry, style, featureId);
} else if (style.graphicName && (style.graphicName != "circle")) {
this.drawNamedSymbol(geometry, style, featureId);
} else {
var pt = this.getLocalXY(geometry);
var p0 = pt[0];
var p1 = pt[1];
if(!isNaN(p0) && !isNaN(p1)) {
var twoPi = Math.PI*2;
var radius = style.pointRadius;
if(style.fill !== false) {
this.setCanvasStyle("fill", style);
this.canvas.beginPath();
this.canvas.arc(p0, p1, radius, 0, twoPi, true);
this.canvas.fill();
if (this.hitDetection) {
this.setHitContextStyle("fill", featureId, style);
this.hitContext.beginPath();
this.hitContext.arc(p0, p1, radius, 0, twoPi, true);
this.hitContext.fill();
}
}
if(style.stroke !== false) {
this.setCanvasStyle("stroke", style);
this.canvas.beginPath();
this.canvas.arc(p0, p1, radius, 0, twoPi, true);
this.canvas.stroke();
if (this.hitDetection) {
this.setHitContextStyle("stroke", featureId, style);
this.hitContext.beginPath();
this.hitContext.arc(p0, p1, radius, 0, twoPi, true);
this.hitContext.stroke();
}
this.setCanvasStyle("reset");
}
}
}
}
},
/**
* Method: drawLineString
* This method is only called by the renderer itself.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
* style - {Object}
* featureId - {String}
*/
drawLineString: function(geometry, style, featureId) {
style = OpenLayers.Util.applyDefaults({fill: false}, style);
this.drawLinearRing(geometry, style, featureId);
},
/**
* Method: drawLinearRing
* This method is only called by the renderer itself.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
* style - {Object}
* featureId - {String}
*/
drawLinearRing: function(geometry, style, featureId) {
if (style.fill !== false) {
this.setCanvasStyle("fill", style);
this.renderPath(this.canvas, geometry, style, featureId, "fill");
if (this.hitDetection) {
this.setHitContextStyle("fill", featureId, style);
this.renderPath(this.hitContext, geometry, style, featureId, "fill");
}
}
if (style.stroke !== false) {
this.setCanvasStyle("stroke", style);
this.renderPath(this.canvas, geometry, style, featureId, "stroke");
if (this.hitDetection) {
this.setHitContextStyle("stroke", featureId, style);
this.renderPath(this.hitContext, geometry, style, featureId, "stroke");
}
}
this.setCanvasStyle("reset");
},
/**
* Method: renderPath
* Render a path with stroke and optional fill.
*/
renderPath: function(context, geometry, style, featureId, type) {
var components = geometry.components;
var len = components.length;
context.beginPath();
var start = this.getLocalXY(components[0]);
var x = start[0];
var y = start[1];
if (!isNaN(x) && !isNaN(y)) {
context.moveTo(start[0], start[1]);
for (var i=1; i<len; ++i) {
var pt = this.getLocalXY(components[i]);
context.lineTo(pt[0], pt[1]);
}
if (type === "fill") {
context.fill();
} else {
context.stroke();
}
}
},
/**
* Method: drawPolygon
* This method is only called by the renderer itself.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
* style - {Object}
* featureId - {String}
*/
drawPolygon: function(geometry, style, featureId) {
var components = geometry.components;
var len = components.length;
this.drawLinearRing(components[0], style, featureId);
// erase inner rings
for (var i=1; i<len; ++i) {
/**
* Note that this is overly aggressive. Here we punch holes through
* all previously rendered features on the same canvas. A better
* solution for polygons with interior rings would be to draw the
* polygon on a sketch canvas first. We could erase all holes
* there and then copy the drawing to the layer canvas.
* TODO: http://trac.osgeo.org/openlayers/ticket/3130
*/
this.canvas.globalCompositeOperation = "destination-out";
if (this.hitDetection) {
this.hitContext.globalCompositeOperation = "destination-out";
}
this.drawLinearRing(
components[i],
OpenLayers.Util.applyDefaults({stroke: false, fillOpacity: 1.0}, style),
featureId
);
this.canvas.globalCompositeOperation = "source-over";
if (this.hitDetection) {
this.hitContext.globalCompositeOperation = "source-over";
}
this.drawLinearRing(
components[i],
OpenLayers.Util.applyDefaults({fill: false}, style),
featureId
);
}
},
/**
* Method: drawText
* This method is only called by the renderer itself.
*
* Parameters:
* location - {<OpenLayers.Point>}
* style - {Object}
*/
drawText: function(location, style) {
var pt = this.getLocalXY(location);
this.setCanvasStyle("reset");
this.canvas.fillStyle = style.fontColor;
this.canvas.globalAlpha = style.fontOpacity || 1.0;
var fontStyle = [style.fontStyle ? style.fontStyle : "normal",
"normal", // "font-variant" not supported
style.fontWeight ? style.fontWeight : "normal",
style.fontSize ? style.fontSize : "1em",
style.fontFamily ? style.fontFamily : "sans-serif"].join(" ");
var labelRows = style.label.split('\n');
var numRows = labelRows.length;
if (this.canvas.fillText) {
// HTML5
this.canvas.font = fontStyle;
this.canvas.textAlign =
OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[0]] ||
"center";
this.canvas.textBaseline =
OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[1]] ||
"middle";
var vfactor =
OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[1]];
if (vfactor == null) {
vfactor = -.5;
}
var lineHeight =
this.canvas.measureText('Mg').height ||
this.canvas.measureText('xx').width;
pt[1] += lineHeight*vfactor*(numRows-1);
for (var i = 0; i < numRows; i++) {
if (style.labelOutlineWidth) {
this.canvas.save();
this.canvas.globalAlpha = style.labelOutlineOpacity || style.fontOpacity || 1.0;
this.canvas.strokeStyle = style.labelOutlineColor;
this.canvas.lineWidth = style.labelOutlineWidth;
this.canvas.strokeText(labelRows[i], pt[0], pt[1] + (lineHeight*i) + 1);
this.canvas.restore();
}
this.canvas.fillText(labelRows[i], pt[0], pt[1] + (lineHeight*i));
}
} else if (this.canvas.mozDrawText) {
// Mozilla pre-Gecko1.9.1 (<FF3.1)
this.canvas.mozTextStyle = fontStyle;
// No built-in text alignment, so we measure and adjust the position
var hfactor =
OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[0]];
if (hfactor == null) {
hfactor = -.5;
}
var vfactor =
OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[1]];
if (vfactor == null) {
vfactor = -.5;
}
var lineHeight = this.canvas.mozMeasureText('xx');
pt[1] += lineHeight*(1 + (vfactor*numRows));
for (var i = 0; i < numRows; i++) {
var x = pt[0] + (hfactor*this.canvas.mozMeasureText(labelRows[i]));
var y = pt[1] + (i*lineHeight);
this.canvas.translate(x, y);
this.canvas.mozDrawText(labelRows[i]);
this.canvas.translate(-x, -y);
}
}
this.setCanvasStyle("reset");
},
/**
* Method: getLocalXY
* transform geographic xy into pixel xy
*
* Parameters:
* point - {<OpenLayers.Geometry.Point>}
*/
getLocalXY: function(point) {
var resolution = this.getResolution();
var extent = this.extent;
var x = ((point.x - this.featureDx) / resolution + (-extent.left / resolution));
var y = ((extent.top / resolution) - point.y / resolution);
return [x, y];
},
/**
* Method: clear
* Clear all vectors from the renderer.
*/
clear: function() {
this.clearCanvas();
this.features = {};
},
/**
* Method: clearCanvas
* Clear the canvas element of the renderer.
*/
clearCanvas: function() {
var height = this.root.height;
var width = this.root.width;
this.canvas.clearRect(0, 0, width, height);
this._clearRectId = OpenLayers.Util.createUniqueID();
if (this.hitDetection) {
this.hitContext.clearRect(0, 0, width, height);
}
},
/**
* Method: getFeatureIdFromEvent
* Returns a feature id from an event on the renderer.
*
* Parameters:
* evt - {<OpenLayers.Event>}
*
* Returns:
* {<OpenLayers.Feature.Vector} A feature or undefined. This method returns a
* feature instead of a feature id to avoid an unnecessary lookup on the
* layer.
*/
getFeatureIdFromEvent: function(evt) {
var featureId, feature;
if (this.hitDetection && this.root.style.display !== "none") {
// this dragging check should go in the feature handler
if (!this.map.dragging) {
var xy = evt.xy;
var x = xy.x | 0;
var y = xy.y | 0;
var data = this.hitContext.getImageData(x, y, 1, 1).data;
if (data[3] === 255) { // antialiased
var id = data[2] + (256 * (data[1] + (256 * data[0])));
if (id) {
featureId = "OpenLayers_Feature_Vector_" + (id - 1 + this.hitOverflow);
try {
feature = this.features[featureId][0];
} catch(err) {
// Because of antialiasing on the canvas, when the hit location is at a point where the edge of
// one symbol intersects the interior of another symbol, a wrong hit color (and therefore id) results.
// todo: set Antialiasing = 'off' on the hitContext as soon as browsers allow it.
}
}
}
}
}
return feature;
},
/**
* Method: eraseFeatures
* This is called by the layer to erase features; removes the feature from
* the list, then redraws the layer.
*
* Parameters:
* features - {Array(<OpenLayers.Feature.Vector>)}
*/
eraseFeatures: function(features) {
if(!(OpenLayers.Util.isArray(features))) {
features = [features];
}
for(var i=0; i<features.length; ++i) {
delete this.features[features[i].id];
}
this.redraw();
},
/**
* Method: redraw
* The real 'meat' of the function: any time things have changed,
* redraw() can be called to loop over all the data and (you guessed
* it) redraw it. Unlike Elements-based Renderers, we can't interact
* with things once they're drawn, to remove them, for example, so
* instead we have to just clear everything and draw from scratch.
*/
redraw: function() {
if (!this.locked) {
this.clearCanvas();
var labelMap = [];
var feature, geometry, style;
var worldBounds = (this.map.baseLayer && this.map.baseLayer.wrapDateLine) && this.map.getMaxExtent();
for (var id in this.features) {
if (!this.features.hasOwnProperty(id)) { continue; }
feature = this.features[id][0];
geometry = feature.geometry;
this.calculateFeatureDx(geometry.getBounds(), worldBounds);
style = this.features[id][1];
this.drawGeometry(geometry, style, feature.id);
if(style.label) {
labelMap.push([feature, style]);
}
}
var item;
for (var i=0, len=labelMap.length; i<len; ++i) {
item = labelMap[i];
this.drawText(item[0].geometry.getCentroid(), item[1]);
}
}
},
CLASS_NAME: "OpenLayers.Renderer.Canvas"
});
/**
* Constant: OpenLayers.Renderer.Canvas.LABEL_ALIGN
* {Object}
*/
OpenLayers.Renderer.Canvas.LABEL_ALIGN = {
"l": "left",
"r": "right",
"t": "top",
"b": "bottom"
};
/**
* Constant: OpenLayers.Renderer.Canvas.LABEL_FACTOR
* {Object}
*/
OpenLayers.Renderer.Canvas.LABEL_FACTOR = {
"l": 0,
"r": -1,
"t": 0,
"b": -1
};
/**
* Constant: OpenLayers.Renderer.Canvas.drawImageScaleFactor
* {Number} Scale factor to apply to the canvas drawImage arguments. This
* is always 1 except for Android 2.1 devices, to work around
* http://code.google.com/p/android/issues/detail?id=5141.
*/
OpenLayers.Renderer.Canvas.drawImageScaleFactor = null;
/* ======================================================================
OpenLayers/Renderer/Elements.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Renderer.js
*/
/**
* Class: OpenLayers.ElementsIndexer
* This class takes care of figuring out which order elements should be
* placed in the DOM based on given indexing methods.
*/
OpenLayers.ElementsIndexer = OpenLayers.Class({
/**
* Property: maxZIndex
* {Integer} This is the largest-most z-index value for a node
* contained within the indexer.
*/
maxZIndex: null,
/**
* Property: order
* {Array<String>} This is an array of node id's stored in the
* order that they should show up on screen. Id's higher up in the
* array (higher array index) represent nodes with higher z-indeces.
*/
order: null,
/**
* Property: indices
* {Object} This is a hash that maps node ids to their z-index value
* stored in the indexer. This is done to make finding a nodes z-index
* value O(1).
*/
indices: null,
/**
* Property: compare
* {Function} This is the function used to determine placement of
* of a new node within the indexer. If null, this defaults to to
* the Z_ORDER_DRAWING_ORDER comparison method.
*/
compare: null,
/**
* APIMethod: initialize
* Create a new indexer with
*
* Parameters:
* yOrdering - {Boolean} Whether to use y-ordering.
*/
initialize: function(yOrdering) {
this.compare = yOrdering ?
OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_Y_ORDER :
OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_DRAWING_ORDER;
this.clear();
},
/**
* APIMethod: insert
* Insert a new node into the indexer. In order to find the correct
* positioning for the node to be inserted, this method uses a binary
* search. This makes inserting O(log(n)).
*
* Parameters:
* newNode - {DOMElement} The new node to be inserted.
*
* Returns
* {DOMElement} the node before which we should insert our newNode, or
* null if newNode can just be appended.
*/
insert: function(newNode) {
// If the node is known to the indexer, remove it so we can
// recalculate where it should go.
if (this.exists(newNode)) {
this.remove(newNode);
}
var nodeId = newNode.id;
this.determineZIndex(newNode);
var leftIndex = -1;
var rightIndex = this.order.length;
var middle;
while (rightIndex - leftIndex > 1) {
middle = parseInt((leftIndex + rightIndex) / 2);
var placement = this.compare(this, newNode,
OpenLayers.Util.getElement(this.order[middle]));
if (placement > 0) {
leftIndex = middle;
} else {
rightIndex = middle;
}
}
this.order.splice(rightIndex, 0, nodeId);
this.indices[nodeId] = this.getZIndex(newNode);
// If the new node should be before another in the index
// order, return the node before which we have to insert the new one;
// else, return null to indicate that the new node can be appended.
return this.getNextElement(rightIndex);
},
/**
* APIMethod: remove
*
* Parameters:
* node - {DOMElement} The node to be removed.
*/
remove: function(node) {
var nodeId = node.id;
var arrayIndex = OpenLayers.Util.indexOf(this.order, nodeId);
if (arrayIndex >= 0) {
// Remove it from the order array, as well as deleting the node
// from the indeces hash.
this.order.splice(arrayIndex, 1);
delete this.indices[nodeId];
// Reset the maxium z-index based on the last item in the
// order array.
if (this.order.length > 0) {
var lastId = this.order[this.order.length - 1];
this.maxZIndex = this.indices[lastId];
} else {
this.maxZIndex = 0;
}
}
},
/**
* APIMethod: clear
*/
clear: function() {
this.order = [];
this.indices = {};
this.maxZIndex = 0;
},
/**
* APIMethod: exists
*
* Parameters:
* node - {DOMElement} The node to test for existence.
*
* Returns:
* {Boolean} Whether or not the node exists in the indexer?
*/
exists: function(node) {
return (this.indices[node.id] != null);
},
/**
* APIMethod: getZIndex
* Get the z-index value for the current node from the node data itself.
*
* Parameters:
* node - {DOMElement} The node whose z-index to get.
*
* Returns:
* {Integer} The z-index value for the specified node (from the node
* data itself).
*/
getZIndex: function(node) {
return node._style.graphicZIndex;
},
/**
* Method: determineZIndex
* Determine the z-index for the current node if there isn't one,
* and set the maximum value if we've found a new maximum.
*
* Parameters:
* node - {DOMElement}
*/
determineZIndex: function(node) {
var zIndex = node._style.graphicZIndex;
// Everything must have a zIndex. If none is specified,
// this means the user *must* (hint: assumption) want this
// node to succomb to drawing order. To enforce drawing order
// over all indexing methods, we'll create a new z-index that's
// greater than any currently in the indexer.
if (zIndex == null) {
zIndex = this.maxZIndex;
node._style.graphicZIndex = zIndex;
} else if (zIndex > this.maxZIndex) {
this.maxZIndex = zIndex;
}
},
/**
* APIMethod: getNextElement
* Get the next element in the order stack.
*
* Parameters:
* index - {Integer} The index of the current node in this.order.
*
* Returns:
* {DOMElement} the node following the index passed in, or
* null.
*/
getNextElement: function(index) {
for (var nextIndex = index + 1, nextElement = undefined;
(nextIndex < this.order.length) && (nextElement == undefined);
nextIndex++) {
nextElement = OpenLayers.Util.getElement(this.order[nextIndex]);
}
return nextElement || null;
},
CLASS_NAME: "OpenLayers.ElementsIndexer"
});
/**
* Namespace: OpenLayers.ElementsIndexer.IndexingMethods
* These are the compare methods for figuring out where a new node should be
* placed within the indexer. These methods are very similar to general
* sorting methods in that they return -1, 0, and 1 to specify the
* direction in which new nodes fall in the ordering.
*/
OpenLayers.ElementsIndexer.IndexingMethods = {
/**
* Method: Z_ORDER
* This compare method is used by other comparison methods.
* It can be used individually for ordering, but is not recommended,
* because it doesn't subscribe to drawing order.
*
* Parameters:
* indexer - {<OpenLayers.ElementsIndexer>}
* newNode - {DOMElement}
* nextNode - {DOMElement}
*
* Returns:
* {Integer}
*/
Z_ORDER: function(indexer, newNode, nextNode) {
var newZIndex = indexer.getZIndex(newNode);
var returnVal = 0;
if (nextNode) {
var nextZIndex = indexer.getZIndex(nextNode);
returnVal = newZIndex - nextZIndex;
}
return returnVal;
},
/**
* APIMethod: Z_ORDER_DRAWING_ORDER
* This method orders nodes by their z-index, but does so in a way
* that, if there are other nodes with the same z-index, the newest
* drawn will be the front most within that z-index. This is the
* default indexing method.
*
* Parameters:
* indexer - {<OpenLayers.ElementsIndexer>}
* newNode - {DOMElement}
* nextNode - {DOMElement}
*
* Returns:
* {Integer}
*/
Z_ORDER_DRAWING_ORDER: function(indexer, newNode, nextNode) {
var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(
indexer,
newNode,
nextNode
);
// Make Z_ORDER subscribe to drawing order by pushing it above
// all of the other nodes with the same z-index.
if (nextNode && returnVal == 0) {
returnVal = 1;
}
return returnVal;
},
/**
* APIMethod: Z_ORDER_Y_ORDER
* This one should really be called Z_ORDER_Y_ORDER_DRAWING_ORDER, as it
* best describes which ordering methods have precedence (though, the
* name would be too long). This method orders nodes by their z-index,
* but does so in a way that, if there are other nodes with the same
* z-index, the nodes with the lower y position will be "closer" than
* those with a higher y position. If two nodes have the exact same y
* position, however, then this method will revert to using drawing
* order to decide placement.
*
* Parameters:
* indexer - {<OpenLayers.ElementsIndexer>}
* newNode - {DOMElement}
* nextNode - {DOMElement}
*
* Returns:
* {Integer}
*/
Z_ORDER_Y_ORDER: function(indexer, newNode, nextNode) {
var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(
indexer,
newNode,
nextNode
);
if (nextNode && returnVal === 0) {
var result = nextNode._boundsBottom - newNode._boundsBottom;
returnVal = (result === 0) ? 1 : result;
}
return returnVal;
}
};
/**
* Class: OpenLayers.Renderer.Elements
* This is another virtual class in that it should never be instantiated by
* itself as a Renderer. It exists because there is *tons* of shared
* functionality between different vector libraries which use nodes/elements
* as a base for rendering vectors.
*
* The highlevel bits of code that are implemented here are the adding and
* removing of geometries, which is essentially the same for any
* element-based renderer. The details of creating each node and drawing the
* paths are of course different, but the machinery is the same.
*
* Inherits:
* - <OpenLayers.Renderer>
*/
OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
/**
* Property: rendererRoot
* {DOMElement}
*/
rendererRoot: null,
/**
* Property: root
* {DOMElement}
*/
root: null,
/**
* Property: vectorRoot
* {DOMElement}
*/
vectorRoot: null,
/**
* Property: textRoot
* {DOMElement}
*/
textRoot: null,
/**
* Property: xmlns
* {String}
*/
xmlns: null,
/**
* Property: xOffset
* {Number} Offset to apply to the renderer viewport translation in x
* direction. If the renderer extent's center is on the right of the
* dateline (i.e. exceeds the world bounds), we shift the viewport to the
* left by one world width. This avoids that features disappear from the
* map viewport. Because our dateline handling logic in other places
* ensures that extents crossing the dateline always have a center
* exceeding the world bounds on the left, we need this offset to make sure
* that the same is true for the renderer extent in pixel space as well.
*/
xOffset: 0,
/**
* Property: rightOfDateLine
* {Boolean} Keeps track of the location of the map extent relative to the
* date line. The <setExtent> method compares this value (which is the one
* from the previous <setExtent> call) with the current position of the map
* extent relative to the date line and updates the xOffset when the extent
* has moved from one side of the date line to the other.
*/
/**
* Property: Indexer
* {<OpenLayers.ElementIndexer>} An instance of OpenLayers.ElementsIndexer
* created upon initialization if the zIndexing or yOrdering options
* passed to this renderer's constructor are set to true.
*/
indexer: null,
/**
* Constant: BACKGROUND_ID_SUFFIX
* {String}
*/
BACKGROUND_ID_SUFFIX: "_background",
/**
* Constant: LABEL_ID_SUFFIX
* {String}
*/
LABEL_ID_SUFFIX: "_label",
/**
* Constant: LABEL_OUTLINE_SUFFIX
* {String}
*/
LABEL_OUTLINE_SUFFIX: "_outline",
/**
* Constructor: OpenLayers.Renderer.Elements
*
* Parameters:
* containerID - {String}
* options - {Object} options for this renderer.
*
* Supported options are:
* yOrdering - {Boolean} Whether to use y-ordering
* zIndexing - {Boolean} Whether to use z-indexing. Will be ignored
* if yOrdering is set to true.
*/
initialize: function(containerID, options) {
OpenLayers.Renderer.prototype.initialize.apply(this, arguments);
this.rendererRoot = this.createRenderRoot();
this.root = this.createRoot("_root");
this.vectorRoot = this.createRoot("_vroot");
this.textRoot = this.createRoot("_troot");
this.root.appendChild(this.vectorRoot);
this.root.appendChild(this.textRoot);
this.rendererRoot.appendChild(this.root);
this.container.appendChild(this.rendererRoot);
if(options && (options.zIndexing || options.yOrdering)) {
this.indexer = new OpenLayers.ElementsIndexer(options.yOrdering);
}
},
/**
* Method: destroy
*/
destroy: function() {
this.clear();
this.rendererRoot = null;
this.root = null;
this.xmlns = null;
OpenLayers.Renderer.prototype.destroy.apply(this, arguments);
},
/**
* Method: clear
* Remove all the elements from the root
*/
clear: function() {
var child;
var root = this.vectorRoot;
if (root) {
while (child = root.firstChild) {
root.removeChild(child);
}
}
root = this.textRoot;
if (root) {
while (child = root.firstChild) {
root.removeChild(child);
}
}
if (this.indexer) {
this.indexer.clear();
}
},
/**
* Method: setExtent
* Set the visible part of the layer.
*
* Parameters:
* extent - {<OpenLayers.Bounds>}
* resolutionChanged - {Boolean}
*
* Returns:
* {Boolean} true to notify the layer that the new extent does not exceed
* the coordinate range, and the features will not need to be redrawn.
* False otherwise.
*/
setExtent: function(extent, resolutionChanged) {
var coordSysUnchanged = OpenLayers.Renderer.prototype.setExtent.apply(this, arguments);
var resolution = this.getResolution();
if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
var rightOfDateLine,
ratio = extent.getWidth() / this.map.getExtent().getWidth(),
extent = extent.scale(1 / ratio),
world = this.map.getMaxExtent();
if (world.right > extent.left && world.right < extent.right) {
rightOfDateLine = true;
} else if (world.left > extent.left && world.left < extent.right) {
rightOfDateLine = false;
}
if (rightOfDateLine !== this.rightOfDateLine || resolutionChanged) {
coordSysUnchanged = false;
this.xOffset = rightOfDateLine === true ?
world.getWidth() / resolution : 0;
}
this.rightOfDateLine = rightOfDateLine;
}
return coordSysUnchanged;
},
/**
* Method: getNodeType
* This function is in charge of asking the specific renderer which type
* of node to create for the given geometry and style. All geometries
* in an Elements-based renderer consist of one node and some
* attributes. We have the nodeFactory() function which creates a node
* for us, but it takes a 'type' as input, and that is precisely what
* this function tells us.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
* style - {Object}
*
* Returns:
* {String} The corresponding node type for the specified geometry
*/
getNodeType: function(geometry, style) { },
/**
* Method: drawGeometry
* Draw the geometry, creating new nodes, setting paths, setting style,
* setting featureId on the node. This method should only be called
* by the renderer itself.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
* style - {Object}
* featureId - {String}
*
* Returns:
* {Boolean} true if the geometry has been drawn completely; null if
* incomplete; false otherwise
*/
drawGeometry: function(geometry, style, featureId) {
var className = geometry.CLASS_NAME;
var rendered = true;
if ((className == "OpenLayers.Geometry.Collection") ||
(className == "OpenLayers.Geometry.MultiPoint") ||
(className == "OpenLayers.Geometry.MultiLineString") ||
(className == "OpenLayers.Geometry.MultiPolygon")) {
for (var i = 0, len=geometry.components.length; i<len; i++) {
rendered = this.drawGeometry(
geometry.components[i], style, featureId) && rendered;
}
return rendered;
}
rendered = false;
var removeBackground = false;
if (style.display != "none") {
if (style.backgroundGraphic) {
this.redrawBackgroundNode(geometry.id, geometry, style,
featureId);
} else {
removeBackground = true;
}
rendered = this.redrawNode(geometry.id, geometry, style,
featureId);
}
if (rendered == false) {
var node = document.getElementById(geometry.id);
if (node) {
if (node._style.backgroundGraphic) {
removeBackground = true;
}
node.parentNode.removeChild(node);
}
}
if (removeBackground) {
var node = document.getElementById(
geometry.id + this.BACKGROUND_ID_SUFFIX);
if (node) {
node.parentNode.removeChild(node);
}
}
return rendered;
},
/**
* Method: redrawNode
*
* Parameters:
* id - {String}
* geometry - {<OpenLayers.Geometry>}
* style - {Object}
* featureId - {String}
*
* Returns:
* {Boolean} true if the complete geometry could be drawn, null if parts of
* the geometry could not be drawn, false otherwise
*/
redrawNode: function(id, geometry, style, featureId) {
style = this.applyDefaultSymbolizer(style);
// Get the node if it's already on the map.
var node = this.nodeFactory(id, this.getNodeType(geometry, style));
// Set the data for the node, then draw it.
node._featureId = featureId;
node._boundsBottom = geometry.getBounds().bottom;
node._geometryClass = geometry.CLASS_NAME;
node._style = style;
var drawResult = this.drawGeometryNode(node, geometry, style);
if(drawResult === false) {
return false;
}
node = drawResult.node;
// Insert the node into the indexer so it can show us where to
// place it. Note that this operation is O(log(n)). If there's a
// performance problem (when dragging, for instance) this is
// likely where it would be.
if (this.indexer) {
var insert = this.indexer.insert(node);
if (insert) {
this.vectorRoot.insertBefore(node, insert);
} else {
this.vectorRoot.appendChild(node);
}
} else {
// if there's no indexer, simply append the node to root,
// but only if the node is a new one
if (node.parentNode !== this.vectorRoot){
this.vectorRoot.appendChild(node);
}
}
this.postDraw(node);
return drawResult.complete;
},
/**
* Method: redrawBackgroundNode
* Redraws the node using special 'background' style properties. Basically
* just calls redrawNode(), but instead of directly using the
* 'externalGraphic', 'graphicXOffset', 'graphicYOffset', and
* 'graphicZIndex' properties directly from the specified 'style'
* parameter, we create a new style object and set those properties
* from the corresponding 'background'-prefixed properties from
* specified 'style' parameter.
*
* Parameters:
* id - {String}
* geometry - {<OpenLayers.Geometry>}
* style - {Object}
* featureId - {String}
*
* Returns:
* {Boolean} true if the complete geometry could be drawn, null if parts of
* the geometry could not be drawn, false otherwise
*/
redrawBackgroundNode: function(id, geometry, style, featureId) {
var backgroundStyle = OpenLayers.Util.extend({}, style);
// Set regular style attributes to apply to the background styles.
backgroundStyle.externalGraphic = backgroundStyle.backgroundGraphic;
backgroundStyle.graphicXOffset = backgroundStyle.backgroundXOffset;
backgroundStyle.graphicYOffset = backgroundStyle.backgroundYOffset;
backgroundStyle.graphicZIndex = backgroundStyle.backgroundGraphicZIndex;
backgroundStyle.graphicWidth = backgroundStyle.backgroundWidth || backgroundStyle.graphicWidth;
backgroundStyle.graphicHeight = backgroundStyle.backgroundHeight || backgroundStyle.graphicHeight;
// Erase background styles.
backgroundStyle.backgroundGraphic = null;
backgroundStyle.backgroundXOffset = null;
backgroundStyle.backgroundYOffset = null;
backgroundStyle.backgroundGraphicZIndex = null;
return this.redrawNode(
id + this.BACKGROUND_ID_SUFFIX,
geometry,
backgroundStyle,
null
);
},
/**
* Method: drawGeometryNode
* Given a node, draw a geometry on the specified layer.
* node and geometry are required arguments, style is optional.
* This method is only called by the render itself.
*
* Parameters:
* node - {DOMElement}
* geometry - {<OpenLayers.Geometry>}
* style - {Object}
*
* Returns:
* {Object} a hash with properties "node" (the drawn node) and "complete"
* (null if parts of the geometry could not be drawn, false if nothing
* could be drawn)
*/
drawGeometryNode: function(node, geometry, style) {
style = style || node._style;
var options = {
'isFilled': style.fill === undefined ?
true :
style.fill,
'isStroked': style.stroke === undefined ?
!!style.strokeWidth :
style.stroke
};
var drawn;
switch (geometry.CLASS_NAME) {
case "OpenLayers.Geometry.Point":
if(style.graphic === false) {
options.isFilled = false;
options.isStroked = false;
}
drawn = this.drawPoint(node, geometry);
break;
case "OpenLayers.Geometry.LineString":
options.isFilled = false;
drawn = this.drawLineString(node, geometry);
break;
case "OpenLayers.Geometry.LinearRing":
drawn = this.drawLinearRing(node, geometry);
break;
case "OpenLayers.Geometry.Polygon":
drawn = this.drawPolygon(node, geometry);
break;
case "OpenLayers.Geometry.Rectangle":
drawn = this.drawRectangle(node, geometry);
break;
default:
break;
}
node._options = options;
//set style
//TBD simplify this
if (drawn != false) {
return {
node: this.setStyle(node, style, options, geometry),
complete: drawn
};
} else {
return false;
}
},
/**
* Method: postDraw
* Things that have do be done after the geometry node is appended
* to its parent node. To be overridden by subclasses.
*
* Parameters:
* node - {DOMElement}
*/
postDraw: function(node) {},
/**
* Method: drawPoint
* Virtual function for drawing Point Geometry.
* Should be implemented by subclasses.
* This method is only called by the renderer itself.
*
* Parameters:
* node - {DOMElement}
* geometry - {<OpenLayers.Geometry>}
*
* Returns:
* {DOMElement} or false if the renderer could not draw the point
*/
drawPoint: function(node, geometry) {},
/**
* Method: drawLineString
* Virtual function for drawing LineString Geometry.
* Should be implemented by subclasses.
* This method is only called by the renderer itself.
*
* Parameters:
* node - {DOMElement}
* geometry - {<OpenLayers.Geometry>}
*
* Returns:
* {DOMElement} or null if the renderer could not draw all components of
* the linestring, or false if nothing could be drawn
*/
drawLineString: function(node, geometry) {},
/**
* Method: drawLinearRing
* Virtual function for drawing LinearRing Geometry.
* Should be implemented by subclasses.
* This method is only called by the renderer itself.
*
* Parameters:
* node - {DOMElement}
* geometry - {<OpenLayers.Geometry>}
*
* Returns:
* {DOMElement} or null if the renderer could not draw all components
* of the linear ring, or false if nothing could be drawn
*/
drawLinearRing: function(node, geometry) {},
/**
* Method: drawPolygon
* Virtual function for drawing Polygon Geometry.
* Should be implemented by subclasses.
* This method is only called by the renderer itself.
*
* Parameters:
* node - {DOMElement}
* geometry - {<OpenLayers.Geometry>}
*
* Returns:
* {DOMElement} or null if the renderer could not draw all components
* of the polygon, or false if nothing could be drawn
*/
drawPolygon: function(node, geometry) {},
/**
* Method: drawRectangle
* Virtual function for drawing Rectangle Geometry.
* Should be implemented by subclasses.
* This method is only called by the renderer itself.
*
* Parameters:
* node - {DOMElement}
* geometry - {<OpenLayers.Geometry>}
*
* Returns:
* {DOMElement} or false if the renderer could not draw the rectangle
*/
drawRectangle: function(node, geometry) {},
/**
* Method: drawCircle
* Virtual function for drawing Circle Geometry.
* Should be implemented by subclasses.
* This method is only called by the renderer itself.
*
* Parameters:
* node - {DOMElement}
* geometry - {<OpenLayers.Geometry>}
*
* Returns:
* {DOMElement} or false if the renderer could not draw the circle
*/
drawCircle: function(node, geometry) {},
/**
* Method: removeText
* Removes a label
*
* Parameters:
* featureId - {String}
*/
removeText: function(featureId) {
var label = document.getElementById(featureId + this.LABEL_ID_SUFFIX);
if (label) {
this.textRoot.removeChild(label);
}
var outline = document.getElementById(featureId + this.LABEL_OUTLINE_SUFFIX);
if (outline) {
this.textRoot.removeChild(outline);
}
},
/**
* Method: getFeatureIdFromEvent
*
* Parameters:
* evt - {Object} An <OpenLayers.Event> object
*
* Returns:
* {String} A feature id or undefined.
*/
getFeatureIdFromEvent: function(evt) {
var target = evt.target;
var useElement = target && target.correspondingUseElement;
var node = useElement ? useElement : (target || evt.srcElement);
return node._featureId;
},
/**
* Method: eraseGeometry
* Erase a geometry from the renderer. In the case of a multi-geometry,
* we cycle through and recurse on ourselves. Otherwise, we look for a
* node with the geometry.id, destroy its geometry, and remove it from
* the DOM.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
* featureId - {String}
*/
eraseGeometry: function(geometry, featureId) {
if ((geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPoint") ||
(geometry.CLASS_NAME == "OpenLayers.Geometry.MultiLineString") ||
(geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPolygon") ||
(geometry.CLASS_NAME == "OpenLayers.Geometry.Collection")) {
for (var i=0, len=geometry.components.length; i<len; i++) {
this.eraseGeometry(geometry.components[i], featureId);
}
} else {
var element = OpenLayers.Util.getElement(geometry.id);
if (element && element.parentNode) {
if (element.geometry) {
element.geometry.destroy();
element.geometry = null;
}
element.parentNode.removeChild(element);
if (this.indexer) {
this.indexer.remove(element);
}
if (element._style.backgroundGraphic) {
var backgroundId = geometry.id + this.BACKGROUND_ID_SUFFIX;
var bElem = OpenLayers.Util.getElement(backgroundId);
if (bElem && bElem.parentNode) {
// No need to destroy the geometry since the element and the background
// node share the same geometry.
bElem.parentNode.removeChild(bElem);
}
}
}
}
},
/**
* Method: nodeFactory
* Create new node of the specified type, with the (optional) specified id.
*
* If node already exists with same ID and a different type, we remove it
* and then call ourselves again to recreate it.
*
* Parameters:
* id - {String}
* type - {String} type Kind of node to draw.
*
* Returns:
* {DOMElement} A new node of the given type and id.
*/
nodeFactory: function(id, type) {
var node = OpenLayers.Util.getElement(id);
if (node) {
if (!this.nodeTypeCompare(node, type)) {
node.parentNode.removeChild(node);
node = this.nodeFactory(id, type);
}
} else {
node = this.createNode(type, id);
}
return node;
},
/**
* Method: nodeTypeCompare
*
* Parameters:
* node - {DOMElement}
* type - {String} Kind of node
*
* Returns:
* {Boolean} Whether or not the specified node is of the specified type
* This function must be overridden by subclasses.
*/
nodeTypeCompare: function(node, type) {},
/**
* Method: createNode
*
* Parameters:
* type - {String} Kind of node to draw.
* id - {String} Id for node.
*
* Returns:
* {DOMElement} A new node of the given type and id.
* This function must be overridden by subclasses.
*/
createNode: function(type, id) {},
/**
* Method: moveRoot
* moves this renderer's root to a different renderer.
*
* Parameters:
* renderer - {<OpenLayers.Renderer>} target renderer for the moved root
*/
moveRoot: function(renderer) {
var root = this.root;
if(renderer.root.parentNode == this.rendererRoot) {
root = renderer.root;
}
root.parentNode.removeChild(root);
renderer.rendererRoot.appendChild(root);
},
/**
* Method: getRenderLayerId
* Gets the layer that this renderer's output appears on. If moveRoot was
* used, this will be different from the id of the layer containing the
* features rendered by this renderer.
*
* Returns:
* {String} the id of the output layer.
*/
getRenderLayerId: function() {
return this.root.parentNode.parentNode.id;
},
/**
* Method: isComplexSymbol
* Determines if a symbol cannot be rendered using drawCircle
*
* Parameters:
* graphicName - {String}
*
* Returns
* {Boolean} true if the symbol is complex, false if not
*/
isComplexSymbol: function(graphicName) {
return (graphicName != "circle") && !!graphicName;
},
CLASS_NAME: "OpenLayers.Renderer.Elements"
});
/* ======================================================================
OpenLayers/Renderer/SVG.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Renderer/Elements.js
*/
/**
* Class: OpenLayers.Renderer.SVG
*
* Inherits:
* - <OpenLayers.Renderer.Elements>
*/
OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
/**
* Property: xmlns
* {String}
*/
xmlns: "http://www.w3.org/2000/svg",
/**
* Property: xlinkns
* {String}
*/
xlinkns: "http://www.w3.org/1999/xlink",
/**
* Constant: MAX_PIXEL
* {Integer} Firefox has a limitation where values larger or smaller than
* about 15000 in an SVG document lock the browser up. This
* works around it.
*/
MAX_PIXEL: 15000,
/**
* Property: translationParameters
* {Object} Hash with "x" and "y" properties
*/
translationParameters: null,
/**
* Property: symbolMetrics
* {Object} Cache for symbol metrics according to their svg coordinate
* space. This is an object keyed by the symbol's id, and values are
* an array of [width, centerX, centerY].
*/
symbolMetrics: null,
/**
* Constructor: OpenLayers.Renderer.SVG
*
* Parameters:
* containerID - {String}
*/
initialize: function(containerID) {
if (!this.supported()) {
return;
}
OpenLayers.Renderer.Elements.prototype.initialize.apply(this,
arguments);
this.translationParameters = {x: 0, y: 0};
this.symbolMetrics = {};
},
/**
* APIMethod: supported
*
* Returns:
* {Boolean} Whether or not the browser supports the SVG renderer
*/
supported: function() {
var svgFeature = "http://www.w3.org/TR/SVG11/feature#";
return (document.implementation &&
(document.implementation.hasFeature("org.w3c.svg", "1.0") ||
document.implementation.hasFeature(svgFeature + "SVG", "1.1") ||
document.implementation.hasFeature(svgFeature + "BasicStructure", "1.1") ));
},
/**
* Method: inValidRange
* See #669 for more information
*
* Parameters:
* x - {Integer}
* y - {Integer}
* xyOnly - {Boolean} whether or not to just check for x and y, which means
* to not take the current translation parameters into account if true.
*
* Returns:
* {Boolean} Whether or not the 'x' and 'y' coordinates are in the
* valid range.
*/
inValidRange: function(x, y, xyOnly) {
var left = x + (xyOnly ? 0 : this.translationParameters.x);
var top = y + (xyOnly ? 0 : this.translationParameters.y);
return (left >= -this.MAX_PIXEL && left <= this.MAX_PIXEL &&
top >= -this.MAX_PIXEL && top <= this.MAX_PIXEL);
},
/**
* Method: setExtent
*
* Parameters:
* extent - {<OpenLayers.Bounds>}
* resolutionChanged - {Boolean}
*
* Returns:
* {Boolean} true to notify the layer that the new extent does not exceed
* the coordinate range, and the features will not need to be redrawn.
* False otherwise.
*/
setExtent: function(extent, resolutionChanged) {
var coordSysUnchanged = OpenLayers.Renderer.Elements.prototype.setExtent.apply(this, arguments);
var resolution = this.getResolution(),
left = -extent.left / resolution,
top = extent.top / resolution;
// If the resolution has changed, start over changing the corner, because
// the features will redraw.
if (resolutionChanged) {
this.left = left;
this.top = top;
// Set the viewbox
var extentString = "0 0 " + this.size.w + " " + this.size.h;
this.rendererRoot.setAttributeNS(null, "viewBox", extentString);
this.translate(this.xOffset, 0);
return true;
} else {
var inRange = this.translate(left - this.left + this.xOffset, top - this.top);
if (!inRange) {
// recenter the coordinate system
this.setExtent(extent, true);
}
return coordSysUnchanged && inRange;
}
},
/**
* Method: translate
* Transforms the SVG coordinate system
*
* Parameters:
* x - {Float}
* y - {Float}
*
* Returns:
* {Boolean} true if the translation parameters are in the valid coordinates
* range, false otherwise.
*/
translate: function(x, y) {
if (!this.inValidRange(x, y, true)) {
return false;
} else {
var transformString = "";
if (x || y) {
transformString = "translate(" + x + "," + y + ")";
}
this.root.setAttributeNS(null, "transform", transformString);
this.translationParameters = {x: x, y: y};
return true;
}
},
/**
* Method: setSize
* Sets the size of the drawing surface.
*
* Parameters:
* size - {<OpenLayers.Size>} The size of the drawing surface
*/
setSize: function(size) {
OpenLayers.Renderer.prototype.setSize.apply(this, arguments);
this.rendererRoot.setAttributeNS(null, "width", this.size.w);
this.rendererRoot.setAttributeNS(null, "height", this.size.h);
},
/**
* Method: getNodeType
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
* style - {Object}
*
* Returns:
* {String} The corresponding node type for the specified geometry
*/
getNodeType: function(geometry, style) {
var nodeType = null;
switch (geometry.CLASS_NAME) {
case "OpenLayers.Geometry.Point":
if (style.externalGraphic) {
nodeType = "image";
} else if (this.isComplexSymbol(style.graphicName)) {
nodeType = "svg";
} else {
nodeType = "circle";
}
break;
case "OpenLayers.Geometry.Rectangle":
nodeType = "rect";
break;
case "OpenLayers.Geometry.LineString":
nodeType = "polyline";
break;
case "OpenLayers.Geometry.LinearRing":
nodeType = "polygon";
break;
case "OpenLayers.Geometry.Polygon":
case "OpenLayers.Geometry.Curve":
nodeType = "path";
break;
default:
break;
}
return nodeType;
},
/**
* Method: setStyle
* Use to set all the style attributes to a SVG node.
*
* Takes care to adjust stroke width and point radius to be
* resolution-relative
*
* Parameters:
* node - {SVGDomElement} An SVG element to decorate
* style - {Object}
* options - {Object} Currently supported options include
* 'isFilled' {Boolean} and
* 'isStroked' {Boolean}
*/
setStyle: function(node, style, options) {
style = style || node._style;
options = options || node._options;
var title = style.title || style.graphicTitle;
if (title) {
node.setAttributeNS(null, "title", title);
//Standards-conformant SVG
// Prevent duplicate nodes. See issue https://github.com/openlayers/ol2/issues/92
var titleNode = node.getElementsByTagName("title");
if (titleNode.length > 0) {
titleNode[0].firstChild.textContent = title;
} else {
var label = this.nodeFactory(null, "title");
label.textContent = title;
node.appendChild(label);
}
}
var r = parseFloat(node.getAttributeNS(null, "r"));
var widthFactor = 1;
var pos;
if (node._geometryClass == "OpenLayers.Geometry.Point" && r) {
node.style.visibility = "";
if (style.graphic === false) {
node.style.visibility = "hidden";
} else if (style.externalGraphic) {
pos = this.getPosition(node);
if (style.graphicWidth && style.graphicHeight) {
node.setAttributeNS(null, "preserveAspectRatio", "none");
}
var width = style.graphicWidth || style.graphicHeight;
var height = style.graphicHeight || style.graphicWidth;
width = width ? width : style.pointRadius*2;
height = height ? height : style.pointRadius*2;
var xOffset = (style.graphicXOffset != undefined) ?
style.graphicXOffset : -(0.5 * width);
var yOffset = (style.graphicYOffset != undefined) ?
style.graphicYOffset : -(0.5 * height);
var opacity = style.graphicOpacity || style.fillOpacity;
node.setAttributeNS(null, "x", (pos.x + xOffset).toFixed());
node.setAttributeNS(null, "y", (pos.y + yOffset).toFixed());
node.setAttributeNS(null, "width", width);
node.setAttributeNS(null, "height", height);
node.setAttributeNS(this.xlinkns, "xlink:href", style.externalGraphic);
node.setAttributeNS(null, "style", "opacity: "+opacity);
node.onclick = OpenLayers.Event.preventDefault;
} else if (this.isComplexSymbol(style.graphicName)) {
// the symbol viewBox is three times as large as the symbol
var offset = style.pointRadius * 3;
var size = offset * 2;
var src = this.importSymbol(style.graphicName);
pos = this.getPosition(node);
widthFactor = this.symbolMetrics[src.id][0] * 3 / size;
// remove the node from the dom before we modify it. This
// prevents various rendering issues in Safari and FF
var parent = node.parentNode;
var nextSibling = node.nextSibling;
if(parent) {
parent.removeChild(node);
}
// The more appropriate way to implement this would be use/defs,
// but due to various issues in several browsers, it is safer to
// copy the symbols instead of referencing them.
// See e.g. ticket http://trac.osgeo.org/openlayers/ticket/2985
// and this email thread
// http://osgeo-org.1803224.n2.nabble.com/Select-Control-Ctrl-click-on-Feature-with-a-graphicName-opens-new-browser-window-tc5846039.html
node.firstChild && node.removeChild(node.firstChild);
node.appendChild(src.firstChild.cloneNode(true));
node.setAttributeNS(null, "viewBox", src.getAttributeNS(null, "viewBox"));
node.setAttributeNS(null, "width", size);
node.setAttributeNS(null, "height", size);
node.setAttributeNS(null, "x", pos.x - offset);
node.setAttributeNS(null, "y", pos.y - offset);
// now that the node has all its new properties, insert it
// back into the dom where it was
if(nextSibling) {
parent.insertBefore(node, nextSibling);
} else if(parent) {
parent.appendChild(node);
}
} else {
node.setAttributeNS(null, "r", style.pointRadius);
}
var rotation = style.rotation;
if ((rotation !== undefined || node._rotation !== undefined) && pos) {
node._rotation = rotation;
rotation |= 0;
if (node.nodeName !== "svg") {
node.setAttributeNS(null, "transform",
"rotate(" + rotation + " " + pos.x + " " +
pos.y + ")");
} else {
var metrics = this.symbolMetrics[src.id];
node.firstChild.setAttributeNS(null, "transform", "rotate("
+ rotation + " "
+ metrics[1] + " "
+ metrics[2] + ")");
}
}
}
if (options.isFilled) {
node.setAttributeNS(null, "fill", style.fillColor);
node.setAttributeNS(null, "fill-opacity", style.fillOpacity);
} else {
node.setAttributeNS(null, "fill", "none");
}
if (options.isStroked) {
node.setAttributeNS(null, "stroke", style.strokeColor);
node.setAttributeNS(null, "stroke-opacity", style.strokeOpacity);
node.setAttributeNS(null, "stroke-width", style.strokeWidth * widthFactor);
node.setAttributeNS(null, "stroke-linecap", style.strokeLinecap || "round");
// Hard-coded linejoin for now, to make it look the same as in VML.
// There is no strokeLinejoin property yet for symbolizers.
node.setAttributeNS(null, "stroke-linejoin", "round");
style.strokeDashstyle && node.setAttributeNS(null,
"stroke-dasharray", this.dashStyle(style, widthFactor));
} else {
node.setAttributeNS(null, "stroke", "none");
}
if (style.pointerEvents) {
node.setAttributeNS(null, "pointer-events", style.pointerEvents);
}
if (style.cursor != null) {
node.setAttributeNS(null, "cursor", style.cursor);
}
return node;
},
/**
* Method: dashStyle
*
* Parameters:
* style - {Object}
* widthFactor - {Number}
*
* Returns:
* {String} A SVG compliant 'stroke-dasharray' value
*/
dashStyle: function(style, widthFactor) {
var w = style.strokeWidth * widthFactor;
var str = style.strokeDashstyle;
switch (str) {
case 'solid':
return 'none';
case 'dot':
return [1, 4 * w].join();
case 'dash':
return [4 * w, 4 * w].join();
case 'dashdot':
return [4 * w, 4 * w, 1, 4 * w].join();
case 'longdash':
return [8 * w, 4 * w].join();
case 'longdashdot':
return [8 * w, 4 * w, 1, 4 * w].join();
default:
return OpenLayers.String.trim(str).replace(/\s+/g, ",");
}
},
/**
* Method: createNode
*
* Parameters:
* type - {String} Kind of node to draw
* id - {String} Id for node
*
* Returns:
* {DOMElement} A new node of the given type and id
*/
createNode: function(type, id) {
var node = document.createElementNS(this.xmlns, type);
if (id) {
node.setAttributeNS(null, "id", id);
}
return node;
},
/**
* Method: nodeTypeCompare
*
* Parameters:
* node - {SVGDomElement} An SVG element
* type - {String} Kind of node
*
* Returns:
* {Boolean} Whether or not the specified node is of the specified type
*/
nodeTypeCompare: function(node, type) {
return (type == node.nodeName);
},
/**
* Method: createRenderRoot
*
* Returns:
* {DOMElement} The specific render engine's root element
*/
createRenderRoot: function() {
var svg = this.nodeFactory(this.container.id + "_svgRoot", "svg");
svg.style.display = "block";
return svg;
},
/**
* Method: createRoot
*
* Parameters:
* suffix - {String} suffix to append to the id
*
* Returns:
* {DOMElement}
*/
createRoot: function(suffix) {
return this.nodeFactory(this.container.id + suffix, "g");
},
/**
* Method: createDefs
*
* Returns:
* {DOMElement} The element to which we'll add the symbol definitions
*/
createDefs: function() {
var defs = this.nodeFactory(this.container.id + "_defs", "defs");
this.rendererRoot.appendChild(defs);
return defs;
},
/**************************************
* *
* GEOMETRY DRAWING FUNCTIONS *
* *
**************************************/
/**
* Method: drawPoint
* This method is only called by the renderer itself.
*
* Parameters:
* node - {DOMElement}
* geometry - {<OpenLayers.Geometry>}
*
* Returns:
* {DOMElement} or false if the renderer could not draw the point
*/
drawPoint: function(node, geometry) {
return this.drawCircle(node, geometry, 1);
},
/**
* Method: drawCircle
* This method is only called by the renderer itself.
*
* Parameters:
* node - {DOMElement}
* geometry - {<OpenLayers.Geometry>}
* radius - {Float}
*
* Returns:
* {DOMElement} or false if the renderer could not draw the circle
*/
drawCircle: function(node, geometry, radius) {
var resolution = this.getResolution();
var x = ((geometry.x - this.featureDx) / resolution + this.left);
var y = (this.top - geometry.y / resolution);
if (this.inValidRange(x, y)) {
node.setAttributeNS(null, "cx", x);
node.setAttributeNS(null, "cy", y);
node.setAttributeNS(null, "r", radius);
return node;
} else {
return false;
}
},
/**
* Method: drawLineString
* This method is only called by the renderer itself.
*
* Parameters:
* node - {DOMElement}
* geometry - {<OpenLayers.Geometry>}
*
* Returns:
* {DOMElement} or null if the renderer could not draw all components of
* the linestring, or false if nothing could be drawn
*/
drawLineString: function(node, geometry) {
var componentsResult = this.getComponentsString(geometry.components);
if (componentsResult.path) {
node.setAttributeNS(null, "points", componentsResult.path);
return (componentsResult.complete ? node : null);
} else {
return false;
}
},
/**
* Method: drawLinearRing
* This method is only called by the renderer itself.
*
* Parameters:
* node - {DOMElement}
* geometry - {<OpenLayers.Geometry>}
*
* Returns:
* {DOMElement} or null if the renderer could not draw all components
* of the linear ring, or false if nothing could be drawn
*/
drawLinearRing: function(node, geometry) {
var componentsResult = this.getComponentsString(geometry.components);
if (componentsResult.path) {
node.setAttributeNS(null, "points", componentsResult.path);
return (componentsResult.complete ? node : null);
} else {
return false;
}
},
/**
* Method: drawPolygon
* This method is only called by the renderer itself.
*
* Parameters:
* node - {DOMElement}
* geometry - {<OpenLayers.Geometry>}
*
* Returns:
* {DOMElement} or null if the renderer could not draw all components
* of the polygon, or false if nothing could be drawn
*/
drawPolygon: function(node, geometry) {
var d = "";
var draw = true;
var complete = true;
var linearRingResult, path;
for (var j=0, len=geometry.components.length; j<len; j++) {
d += " M";
linearRingResult = this.getComponentsString(
geometry.components[j].components, " ");
path = linearRingResult.path;
if (path) {
d += " " + path;
complete = linearRingResult.complete && complete;
} else {
draw = false;
}
}
d += " z";
if (draw) {
node.setAttributeNS(null, "d", d);
node.setAttributeNS(null, "fill-rule", "evenodd");
return complete ? node : null;
} else {
return false;
}
},
/**
* Method: drawRectangle
* This method is only called by the renderer itself.
*
* Parameters:
* node - {DOMElement}
* geometry - {<OpenLayers.Geometry>}
*
* Returns:
* {DOMElement} or false if the renderer could not draw the rectangle
*/
drawRectangle: function(node, geometry) {
var resolution = this.getResolution();
var x = ((geometry.x - this.featureDx) / resolution + this.left);
var y = (this.top - geometry.y / resolution);
if (this.inValidRange(x, y)) {
node.setAttributeNS(null, "x", x);
node.setAttributeNS(null, "y", y);
node.setAttributeNS(null, "width", geometry.width / resolution);
node.setAttributeNS(null, "height", geometry.height / resolution);
return node;
} else {
return false;
}
},
/**
* Method: drawText
* This method is only called by the renderer itself.
*
* Parameters:
* featureId - {String}
* style -
* location - {<OpenLayers.Geometry.Point>}
*/
drawText: function(featureId, style, location) {
var drawOutline = (!!style.labelOutlineWidth);
// First draw text in halo color and size and overlay the
// normal text afterwards
if (drawOutline) {
var outlineStyle = OpenLayers.Util.extend({}, style);
outlineStyle.fontColor = outlineStyle.labelOutlineColor;
outlineStyle.fontStrokeColor = outlineStyle.labelOutlineColor;
outlineStyle.fontStrokeWidth = style.labelOutlineWidth;
if (style.labelOutlineOpacity) {
outlineStyle.fontOpacity = style.labelOutlineOpacity;
}
delete outlineStyle.labelOutlineWidth;
this.drawText(featureId, outlineStyle, location);
}
var resolution = this.getResolution();
var x = ((location.x - this.featureDx) / resolution + this.left);
var y = (location.y / resolution - this.top);
var suffix = (drawOutline)?this.LABEL_OUTLINE_SUFFIX:this.LABEL_ID_SUFFIX;
var label = this.nodeFactory(featureId + suffix, "text");
label.setAttributeNS(null, "x", x);
label.setAttributeNS(null, "y", -y);
if (style.fontColor) {
label.setAttributeNS(null, "fill", style.fontColor);
}
if (style.fontStrokeColor) {
label.setAttributeNS(null, "stroke", style.fontStrokeColor);
}
if (style.fontStrokeWidth) {
label.setAttributeNS(null, "stroke-width", style.fontStrokeWidth);
}
if (style.fontOpacity) {
label.setAttributeNS(null, "opacity", style.fontOpacity);
}
if (style.fontFamily) {
label.setAttributeNS(null, "font-family", style.fontFamily);
}
if (style.fontSize) {
label.setAttributeNS(null, "font-size", style.fontSize);
}
if (style.fontWeight) {
label.setAttributeNS(null, "font-weight", style.fontWeight);
}
if (style.fontStyle) {
label.setAttributeNS(null, "font-style", style.fontStyle);
}
if (style.labelSelect === true) {
label.setAttributeNS(null, "pointer-events", "visible");
label._featureId = featureId;
} else {
label.setAttributeNS(null, "pointer-events", "none");
}
var align = style.labelAlign || OpenLayers.Renderer.defaultSymbolizer.labelAlign;
label.setAttributeNS(null, "text-anchor",
OpenLayers.Renderer.SVG.LABEL_ALIGN[align[0]] || "middle");
if (OpenLayers.IS_GECKO === true) {
label.setAttributeNS(null, "dominant-baseline",
OpenLayers.Renderer.SVG.LABEL_ALIGN[align[1]] || "central");
}
var labelRows = style.label.split('\n');
var numRows = labelRows.length;
while (label.childNodes.length > numRows) {
label.removeChild(label.lastChild);
}
for (var i = 0; i < numRows; i++) {
var tspan = this.nodeFactory(featureId + suffix + "_tspan_" + i, "tspan");
if (style.labelSelect === true) {
tspan._featureId = featureId;
tspan._geometry = location;
tspan._geometryClass = location.CLASS_NAME;
}
if (OpenLayers.IS_GECKO === false) {
tspan.setAttributeNS(null, "baseline-shift",
OpenLayers.Renderer.SVG.LABEL_VSHIFT[align[1]] || "-35%");
}
tspan.setAttribute("x", x);
if (i == 0) {
var vfactor = OpenLayers.Renderer.SVG.LABEL_VFACTOR[align[1]];
if (vfactor == null) {
vfactor = -.5;
}
tspan.setAttribute("dy", (vfactor*(numRows-1)) + "em");
} else {
tspan.setAttribute("dy", "1em");
}
tspan.textContent = (labelRows[i] === '') ? ' ' : labelRows[i];
if (!tspan.parentNode) {
label.appendChild(tspan);
}
}
if (!label.parentNode) {
this.textRoot.appendChild(label);
}
},
/**
* Method: getComponentString
*
* Parameters:
* components - {Array(<OpenLayers.Geometry.Point>)} Array of points
* separator - {String} character between coordinate pairs. Defaults to ","
*
* Returns:
* {Object} hash with properties "path" (the string created from the
* components and "complete" (false if the renderer was unable to
* draw all components)
*/
getComponentsString: function(components, separator) {
var renderCmp = [];
var complete = true;
var len = components.length;
var strings = [];
var str, component;
for(var i=0; i<len; i++) {
component = components[i];
renderCmp.push(component);
str = this.getShortString(component);
if (str) {
strings.push(str);
} else {
// The current component is outside the valid range. Let's
// see if the previous or next component is inside the range.
// If so, add the coordinate of the intersection with the
// valid range bounds.
if (i > 0) {
if (this.getShortString(components[i - 1])) {
strings.push(this.clipLine(components[i],
components[i-1]));
}
}
if (i < len - 1) {
if (this.getShortString(components[i + 1])) {
strings.push(this.clipLine(components[i],
components[i+1]));
}
}
complete = false;
}
}
return {
path: strings.join(separator || ","),
complete: complete
};
},
/**
* Method: clipLine
* Given two points (one inside the valid range, and one outside),
* clips the line betweeen the two points so that the new points are both
* inside the valid range.
*
* Parameters:
* badComponent - {<OpenLayers.Geometry.Point>} original geometry of the
* invalid point
* goodComponent - {<OpenLayers.Geometry.Point>} original geometry of the
* valid point
* Returns
* {String} the SVG coordinate pair of the clipped point (like
* getShortString), or an empty string if both passed componets are at
* the same point.
*/
clipLine: function(badComponent, goodComponent) {
if (goodComponent.equals(badComponent)) {
return "";
}
var resolution = this.getResolution();
var maxX = this.MAX_PIXEL - this.translationParameters.x;
var maxY = this.MAX_PIXEL - this.translationParameters.y;
var x1 = (goodComponent.x - this.featureDx) / resolution + this.left;
var y1 = this.top - goodComponent.y / resolution;
var x2 = (badComponent.x - this.featureDx) / resolution + this.left;
var y2 = this.top - badComponent.y / resolution;
var k;
if (x2 < -maxX || x2 > maxX) {
k = (y2 - y1) / (x2 - x1);
x2 = x2 < 0 ? -maxX : maxX;
y2 = y1 + (x2 - x1) * k;
}
if (y2 < -maxY || y2 > maxY) {
k = (x2 - x1) / (y2 - y1);
y2 = y2 < 0 ? -maxY : maxY;
x2 = x1 + (y2 - y1) * k;
}
return x2 + "," + y2;
},
/**
* Method: getShortString
*
* Parameters:
* point - {<OpenLayers.Geometry.Point>}
*
* Returns:
* {String} or false if point is outside the valid range
*/
getShortString: function(point) {
var resolution = this.getResolution();
var x = ((point.x - this.featureDx) / resolution + this.left);
var y = (this.top - point.y / resolution);
if (this.inValidRange(x, y)) {
return x + "," + y;
} else {
return false;
}
},
/**
* Method: getPosition
* Finds the position of an svg node.
*
* Parameters:
* node - {DOMElement}
*
* Returns:
* {Object} hash with x and y properties, representing the coordinates
* within the svg coordinate system
*/
getPosition: function(node) {
return({
x: parseFloat(node.getAttributeNS(null, "cx")),
y: parseFloat(node.getAttributeNS(null, "cy"))
});
},
/**
* Method: importSymbol
* add a new symbol definition from the rendererer's symbol hash
*
* Parameters:
* graphicName - {String} name of the symbol to import
*
* Returns:
* {DOMElement} - the imported symbol
*/
importSymbol: function (graphicName) {
if (!this.defs) {
// create svg defs tag
this.defs = this.createDefs();
}
var id = this.container.id + "-" + graphicName;
// check if symbol already exists in the defs
var existing = document.getElementById(id);
if (existing != null) {
return existing;
}
var symbol = OpenLayers.Renderer.symbol[graphicName];
if (!symbol) {
throw new Error(graphicName + ' is not a valid symbol name');
}
var symbolNode = this.nodeFactory(id, "symbol");
var node = this.nodeFactory(null, "polygon");
symbolNode.appendChild(node);
var symbolExtent = new OpenLayers.Bounds(
Number.MAX_VALUE, Number.MAX_VALUE, 0, 0);
var points = [];
var x,y;
for (var i=0; i<symbol.length; i=i+2) {
x = symbol[i];
y = symbol[i+1];
symbolExtent.left = Math.min(symbolExtent.left, x);
symbolExtent.bottom = Math.min(symbolExtent.bottom, y);
symbolExtent.right = Math.max(symbolExtent.right, x);
symbolExtent.top = Math.max(symbolExtent.top, y);
points.push(x, ",", y);
}
node.setAttributeNS(null, "points", points.join(" "));
var width = symbolExtent.getWidth();
var height = symbolExtent.getHeight();
// create a viewBox three times as large as the symbol itself,
// to allow for strokeWidth being displayed correctly at the corners.
var viewBox = [symbolExtent.left - width,
symbolExtent.bottom - height, width * 3, height * 3];
symbolNode.setAttributeNS(null, "viewBox", viewBox.join(" "));
this.symbolMetrics[id] = [
Math.max(width, height),
symbolExtent.getCenterLonLat().lon,
symbolExtent.getCenterLonLat().lat
];
this.defs.appendChild(symbolNode);
return symbolNode;
},
/**
* Method: getFeatureIdFromEvent
*
* Parameters:
* evt - {Object} An <OpenLayers.Event> object
*
* Returns:
* {String} A feature id or undefined.
*/
getFeatureIdFromEvent: function(evt) {
var featureId = OpenLayers.Renderer.Elements.prototype.getFeatureIdFromEvent.apply(this, arguments);
if(!featureId) {
var target = evt.target;
featureId = target.parentNode && target != this.rendererRoot ?
target.parentNode._featureId : undefined;
}
return featureId;
},
CLASS_NAME: "OpenLayers.Renderer.SVG"
});
/**
* Constant: OpenLayers.Renderer.SVG.LABEL_ALIGN
* {Object}
*/
OpenLayers.Renderer.SVG.LABEL_ALIGN = {
"l": "start",
"r": "end",
"b": "bottom",
"t": "hanging"
};
/**
* Constant: OpenLayers.Renderer.SVG.LABEL_VSHIFT
* {Object}
*/
OpenLayers.Renderer.SVG.LABEL_VSHIFT = {
// according to
// http://www.w3.org/Graphics/SVG/Test/20061213/htmlObjectHarness/full-text-align-02-b.html
// a baseline-shift of -70% shifts the text exactly from the
// bottom to the top of the baseline, so -35% moves the text to
// the center of the baseline.
"t": "-70%",
"b": "0"
};
/**
* Constant: OpenLayers.Renderer.SVG.LABEL_VFACTOR
* {Object}
*/
OpenLayers.Renderer.SVG.LABEL_VFACTOR = {
"t": 0,
"b": -1
};
/**
* Function: OpenLayers.Renderer.SVG.preventDefault
* *Deprecated*. Use <OpenLayers.Event.preventDefault> method instead.
* Used to prevent default events (especially opening images in a new tab on
* ctrl-click) from being executed for externalGraphic symbols
*/
OpenLayers.Renderer.SVG.preventDefault = function(e) {
OpenLayers.Event.preventDefault(e);
};
/* ======================================================================
OpenLayers/Renderer/VML.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published 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.VML
* Render vector features in browsers with VML capability. Construct a new
* VML renderer with the <OpenLayers.Renderer.VML> 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.Elements>
*/
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 - {<OpenLayers.Bounds>}
* resolutionChanged - {Boolean}
*
* Returns:
* {Boolean} true to notify the layer that the new extent does not exceed
* the coordinate range, and the features will not need to be redrawn.
*/
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<len; ++i) {
root = roots[i];
var size = this.size.w + " " + this.size.h;
root.coordsize = size;
}
// flip the VML display Y axis upside down so it
// matches the display Y axis of the map
this.root.style.flip = "y";
return coordSysUnchanged;
},
/**
* Method: setSize
* Set the size of the drawing surface
*
* Parameters:
* size - {<OpenLayers.Size>} the size of the drawing surface
*/
setSize: function(size) {
OpenLayers.Renderer.prototype.setSize.apply(this, arguments);
// 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<len; ++i) {
root = roots[i];
root.style.width = w;
root.style.height = h;
}
},
/**
* Method: getNodeType
* Get the node type for a geometry and style
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
* style - {Object}
*
* Returns:
* {String} The corresponding node type for the specified geometry
*/
getNodeType: function(geometry, style) {
var nodeType = null;
switch (geometry.CLASS_NAME) {
case "OpenLayers.Geometry.Point":
if (style.externalGraphic) {
nodeType = "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 - {<OpenLayers.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 - {<OpenLayers.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 - {<OpenLayers.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 - {<OpenLayers.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 - {<OpenLayers.Geometry>}
*
* Returns:
* {DOMElement}
*/
drawLineString: function(node, geometry) {
return this.drawLine(node, geometry, false);
},
/**
* Method: drawLinearRing
* Render a linearring
*
* Parameters:
* node - {DOMElement}
* geometry - {<OpenLayers.Geometry>}
*
* Returns:
* {DOMElement}
*/
drawLinearRing: function(node, geometry) {
return this.drawLine(node, geometry, true);
},
/**
* Method: DrawLine
* Render a line.
*
* Parameters:
* node - {DOMElement}
* geometry - {<OpenLayers.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 - {<OpenLayers.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<jj; j++) {
path.push("m");
points = geometry.components[j].components;
// we only close paths of interior rings with area
area = (j === 0);
first = null;
second = null;
for (i=0, ii=points.length; i<ii; i++) {
comp = points[i];
x = ((comp.x - this.featureDx) / resolution - this.offset.x) | 0;
y = (comp.y / resolution - this.offset.y) | 0;
pathComp = " " + x + "," + y;
path.push(pathComp);
if (i==0) {
path.push(" l");
}
if (!area) {
// IE improperly renders sub-paths that have no area.
// Instead of checking the area of every ring, we confirm
// the ring has at least three distinct points. This does
// not catch all non-zero area cases, but it greatly improves
// interior ring digitizing and is a minor performance hit
// when rendering rings with many points.
if (!first) {
first = pathComp;
} else if (first != pathComp) {
if (!second) {
second = pathComp;
} else if (second != pathComp) {
// stop looking
area = true;
}
}
}
}
path.push(area ? " x " : " ");
}
path.push("e");
node.path = path.join("");
return node;
},
/**
* Method: drawRectangle
* Render a rectangle
*
* Parameters:
* node - {DOMElement}
* geometry - {<OpenLayers.Geometry>}
*
* 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 - {<OpenLayers.Geometry.Point>}
*/
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 - {<OpenLayers.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<symbol.length; i=i+2) {
var x = symbol[i];
var y = symbol[i+1];
symbolExtent.left = Math.min(symbolExtent.left, x);
symbolExtent.bottom = Math.min(symbolExtent.bottom, y);
symbolExtent.right = Math.max(symbolExtent.right, x);
symbolExtent.top = Math.max(symbolExtent.top, y);
pathitems.push(x);
pathitems.push(y);
if (i == 0) {
pathitems.push("l");
}
}
pathitems.push("x e");
var path = pathitems.join(" ");
var diff = (symbolExtent.getWidth() - symbolExtent.getHeight()) / 2;
if(diff > 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-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/BaseTypes/Class.js
*/
/**
* Class: OpenLayers.Protocol
* Abstract vector layer protocol class. Not to be instantiated directly. Use
* one of the protocol subclasses instead.
*/
OpenLayers.Protocol = OpenLayers.Class({
/**
* Property: format
* {<OpenLayers.Format>} The format used by this protocol.
*/
format: null,
/**
* Property: options
* {Object} Any options sent to the constructor.
*/
options: null,
/**
* Property: autoDestroy
* {Boolean} The creator of the protocol can set autoDestroy to false
* to fully control when the protocol is destroyed. Defaults to
* true.
*/
autoDestroy: true,
/**
* Property: defaultFilter
* {<OpenLayers.Filter>} Optional default filter to read requests
*/
defaultFilter: null,
/**
* Constructor: OpenLayers.Protocol
* Abstract class for vector protocols. Create instances of a subclass.
*
* Parameters:
* options - {Object} Optional object whose properties will be set on the
* instance.
*/
initialize: function(options) {
options = options || {};
OpenLayers.Util.extend(this, options);
this.options = options;
},
/**
* Method: mergeWithDefaultFilter
* Merge filter passed to the read method with the default one
*
* Parameters:
* filter - {<OpenLayers.Filter>}
*/
mergeWithDefaultFilter: function(filter) {
var merged;
if (filter && this.defaultFilter) {
merged = new OpenLayers.Filter.Logical({
type: OpenLayers.Filter.Logical.AND,
filters: [this.defaultFilter, filter]
});
} else {
merged = filter || this.defaultFilter || undefined;
}
return merged;
},
/**
* APIMethod: destroy
* Clean up the protocol.
*/
destroy: function() {
this.options = null;
this.format = null;
},
/**
* APIMethod: read
* Construct a request for reading new features.
*
* Parameters:
* options - {Object} Optional object for configuring the request.
*
* Returns:
* {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
* object, the same object will be passed to the callback function passed
* if one exists in the options object.
*/
read: function(options) {
options = options || {};
options.filter = this.mergeWithDefaultFilter(options.filter);
},
/**
* APIMethod: create
* Construct a request for writing newly created features.
*
* Parameters:
* features - {Array({<OpenLayers.Feature.Vector>})} or
* {<OpenLayers.Feature.Vector>}
* options - {Object} Optional object for configuring the request.
*
* Returns:
* {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
* object, the same object will be passed to the callback function passed
* if one exists in the options object.
*/
create: function() {
},
/**
* APIMethod: update
* Construct a request updating modified features.
*
* Parameters:
* features - {Array({<OpenLayers.Feature.Vector>})} or
* {<OpenLayers.Feature.Vector>}
* options - {Object} Optional object for configuring the request.
*
* Returns:
* {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
* object, the same object will be passed to the callback function passed
* if one exists in the options object.
*/
update: function() {
},
/**
* APIMethod: delete
* Construct a request deleting a removed feature.
*
* Parameters:
* feature - {<OpenLayers.Feature.Vector>}
* options - {Object} Optional object for configuring the request.
*
* Returns:
* {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
* object, the same object will be passed to the callback function passed
* if one exists in the options object.
*/
"delete": function() {
},
/**
* APIMethod: commit
* Go over the features and for each take action
* based on the feature state. Possible actions are create,
* update and delete.
*
* Parameters:
* features - {Array({<OpenLayers.Feature.Vector>})}
* options - {Object} Object whose possible keys are "create", "update",
* "delete", "callback" and "scope", the values referenced by the
* first three are objects as passed to the "create", "update", and
* "delete" methods, the value referenced by the "callback" key is
* a function which is called when the commit operation is complete
* using the scope referenced by the "scope" key.
*
* Returns:
* {Array({<OpenLayers.Protocol.Response>})} An array of
* <OpenLayers.Protocol.Response> objects.
*/
commit: function() {
},
/**
* Method: abort
* Abort an ongoing request.
*
* Parameters:
* response - {<OpenLayers.Protocol.Response>}
*/
abort: function(response) {
},
/**
* Method: createCallback
* Returns a function that applies the given public method with resp and
* options arguments.
*
* Parameters:
* method - {Function} The method to be applied by the callback.
* response - {<OpenLayers.Protocol.Response>} The protocol response object.
* options - {Object} Options sent to the protocol method
*/
createCallback: function(method, response, options) {
return OpenLayers.Function.bind(function() {
method.apply(this, [response, options]);
}, this);
},
CLASS_NAME: "OpenLayers.Protocol"
});
/**
* Class: OpenLayers.Protocol.Response
* Protocols return Response objects to their users.
*/
OpenLayers.Protocol.Response = OpenLayers.Class({
/**
* Property: code
* {Number} - OpenLayers.Protocol.Response.SUCCESS or
* OpenLayers.Protocol.Response.FAILURE
*/
code: null,
/**
* Property: requestType
* {String} The type of request this response corresponds to. Either
* "create", "read", "update" or "delete".
*/
requestType: null,
/**
* Property: last
* {Boolean} - true if this is the last response expected in a commit,
* false otherwise, defaults to true.
*/
last: true,
/**
* Property: features
* {Array({<OpenLayers.Feature.Vector>})} or {<OpenLayers.Feature.Vector>}
* The features returned in the response by the server. Depending on the
* protocol's read payload, either features or data will be populated.
*/
features: null,
/**
* Property: data
* {Object}
* The data returned in the response by the server. Depending on the
* protocol's read payload, either features or data will be populated.
*/
data: null,
/**
* Property: reqFeatures
* {Array({<OpenLayers.Feature.Vector>})} or {<OpenLayers.Feature.Vector>}
* The features provided by the user and placed in the request by the
* protocol.
*/
reqFeatures: null,
/**
* Property: priv
*/
priv: null,
/**
* Property: error
* {Object} The error object in case a service exception was encountered.
*/
error: null,
/**
* Constructor: OpenLayers.Protocol.Response
*
* Parameters:
* options - {Object} Optional object whose properties will be set on the
* instance.
*/
initialize: function(options) {
OpenLayers.Util.extend(this, options);
},
/**
* Method: success
*
* Returns:
* {Boolean} - true on success, false otherwise
*/
success: function() {
return this.code > 0;
},
CLASS_NAME: "OpenLayers.Protocol.Response"
});
OpenLayers.Protocol.Response.SUCCESS = 1;
OpenLayers.Protocol.Response.FAILURE = 0;
/* ======================================================================
OpenLayers/Request.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Events.js
* @requires OpenLayers/Request/XMLHttpRequest.js
*/
/**
* TODO: deprecate me
* Use OpenLayers.Request.proxy instead.
*/
OpenLayers.ProxyHost = "";
/**
* Namespace: OpenLayers.Request
* The OpenLayers.Request namespace contains convenience methods for working
* with XMLHttpRequests. These methods work with a cross-browser
* W3C compliant <OpenLayers.Request.XMLHttpRequest> class.
*/
if (!OpenLayers.Request) {
/**
* This allows for OpenLayers/Request/XMLHttpRequest.js to be included
* before or after this script.
*/
OpenLayers.Request = {};
}
OpenLayers.Util.extend(OpenLayers.Request, {
/**
* Constant: DEFAULT_CONFIG
* {Object} Default configuration for all requests.
*/
DEFAULT_CONFIG: {
method: "GET",
url: window.location.href,
async: true,
user: undefined,
password: undefined,
params: null,
proxy: OpenLayers.ProxyHost,
headers: {},
data: null,
callback: function() {},
success: null,
failure: null,
scope: null
},
/**
* Constant: URL_SPLIT_REGEX
*/
URL_SPLIT_REGEX: /([^:]*:)\/\/([^:]*:?[^@]*@)?([^:\/\?]*):?([^\/\?]*)/,
/**
* APIProperty: events
* {<OpenLayers.Events>} An events object that handles all
* events on the {<OpenLayers.Request>} object.
*
* All event listeners will receive an event object with three properties:
* request - {<OpenLayers.Request.XMLHttpRequest>} The request object.
* config - {Object} The config object sent to the specific request method.
* requestUrl - {String} The request url.
*
* Supported event types:
* complete - Triggered when we have a response from the request, if a
* listener returns false, no further response processing will take
* place.
* success - Triggered when the HTTP response has a success code (200-299).
* failure - Triggered when the HTTP response does not have a success code.
*/
events: new OpenLayers.Events(this),
/**
* Method: makeSameOrigin
* Using the specified proxy, returns a same origin url of the provided url.
*
* Parameters:
* url - {String} An arbitrary url
* proxy {String|Function} The proxy to use to make the provided url a
* same origin url.
*
* Returns
* {String} the same origin url. If no proxy is provided, the returned url
* will be the same as the provided url.
*/
makeSameOrigin: function(url, proxy) {
var sameOrigin = url.indexOf("http") !== 0;
var urlParts = !sameOrigin && url.match(this.URL_SPLIT_REGEX);
if (urlParts) {
var location = window.location;
sameOrigin =
urlParts[1] == location.protocol &&
urlParts[3] == location.hostname;
var uPort = urlParts[4], lPort = location.port;
if (uPort != 80 && uPort != "" || lPort != "80" && lPort != "") {
sameOrigin = sameOrigin && uPort == lPort;
}
}
if (!sameOrigin) {
if (proxy) {
if (typeof proxy == "function") {
url = proxy(url);
} else {
url = proxy + encodeURIComponent(url);
}
}
}
return url;
},
/**
* APIMethod: issue
* Create a new XMLHttpRequest object, open it, set any headers, bind
* a callback to done state, and send any data. It is recommended that
* you use one <GET>, <POST>, <PUT>, <DELETE>, <OPTIONS>, or <HEAD>.
* This method is only documented to provide detail on the configuration
* options available to all request methods.
*
* Parameters:
* config - {Object} Object containing properties for configuring the
* request. Allowed configuration properties are described below.
* This object is modified and should not be reused.
*
* Allowed config properties:
* method - {String} One of GET, POST, PUT, DELETE, HEAD, or
* OPTIONS. Default is GET.
* url - {String} URL for the request.
* async - {Boolean} Open an asynchronous request. Default is true.
* user - {String} User for relevant authentication scheme. Set
* to null to clear current user.
* password - {String} Password for relevant authentication scheme.
* Set to null to clear current password.
* proxy - {String} Optional proxy. Defaults to
* <OpenLayers.ProxyHost>.
* params - {Object} Any key:value pairs to be appended to the
* url as a query string. Assumes url doesn't already include a query
* string or hash. Typically, this is only appropriate for <GET>
* requests where the query string will be appended to the url.
* Parameter values that are arrays will be
* concatenated with a comma (note that this goes against form-encoding)
* as is done with <OpenLayers.Util.getParameterString>.
* headers - {Object} Object with header:value pairs to be set on
* the request.
* data - {String | Document} Optional data to send with the request.
* Typically, this is only used with <POST> and <PUT> requests.
* Make sure to provide the appropriate "Content-Type" header for your
* data. For <POST> and <PUT> requests, the content type defaults to
* "application-xml". If your data is a different content type, or
* if you are using a different HTTP method, set the "Content-Type"
* header to match your data type.
* callback - {Function} Function to call when request is done.
* To determine if the request failed, check request.status (200
* indicates success).
* success - {Function} Optional function to call if request status is in
* the 200s. This will be called in addition to callback above and
* would typically only be used as an alternative.
* failure - {Function} Optional function to call if request status is not
* in the 200s. This will be called in addition to callback above and
* would typically only be used as an alternative.
* scope - {Object} If callback is a public method on some object,
* set the scope to that object.
*
* Returns:
* {XMLHttpRequest} Request object. To abort the request before a response
* is received, call abort() on the request object.
*/
issue: function(config) {
// apply default config - proxy host may have changed
var defaultConfig = OpenLayers.Util.extend(
this.DEFAULT_CONFIG,
{proxy: OpenLayers.ProxyHost}
);
config = config || {};
config.headers = config.headers || {};
config = OpenLayers.Util.applyDefaults(config, defaultConfig);
config.headers = OpenLayers.Util.applyDefaults(config.headers, defaultConfig.headers);
// Always set the "X-Requested-With" header to signal that this request
// was issued through the XHR-object. Since header keys are case
// insensitive and we want to allow overriding of the "X-Requested-With"
// header through the user we cannot use applyDefaults, but have to
// check manually whether we were called with a "X-Requested-With"
// header.
var customRequestedWithHeader = false,
headerKey;
for(headerKey in config.headers) {
if (config.headers.hasOwnProperty( headerKey )) {
if (headerKey.toLowerCase() === 'x-requested-with') {
customRequestedWithHeader = true;
}
}
}
if (customRequestedWithHeader === false) {
// we did not have a custom "X-Requested-With" header
config.headers['X-Requested-With'] = 'XMLHttpRequest';
}
// create request, open, and set headers
var request = new OpenLayers.Request.XMLHttpRequest();
var url = OpenLayers.Util.urlAppend(config.url,
OpenLayers.Util.getParameterString(config.params || {}));
url = OpenLayers.Request.makeSameOrigin(url, config.proxy);
request.open(
config.method, url, config.async, config.user, config.password
);
for(var header in config.headers) {
request.setRequestHeader(header, config.headers[header]);
}
var events = this.events;
// we want to execute runCallbacks with "this" as the
// execution scope
var self = this;
request.onreadystatechange = function() {
if(request.readyState == OpenLayers.Request.XMLHttpRequest.DONE) {
var proceed = events.triggerEvent(
"complete",
{request: request, config: config, requestUrl: url}
);
if(proceed !== false) {
self.runCallbacks(
{request: request, config: config, requestUrl: url}
);
}
}
};
// send request (optionally with data) and return
// call in a timeout for asynchronous requests so the return is
// available before readyState == 4 for cached docs
if(config.async === false) {
request.send(config.data);
} else {
window.setTimeout(function(){
if (request.readyState !== 0) { // W3C: 0-UNSENT
request.send(config.data);
}
}, 0);
}
return request;
},
/**
* Method: runCallbacks
* Calls the complete, success and failure callbacks. Application
* can listen to the "complete" event, have the listener
* display a confirm window and always return false, and
* execute OpenLayers.Request.runCallbacks if the user
* hits "yes" in the confirm window.
*
* Parameters:
* options - {Object} Hash containing request, config and requestUrl keys
*/
runCallbacks: function(options) {
var request = options.request;
var config = options.config;
// bind callbacks to readyState 4 (done)
var complete = (config.scope) ?
OpenLayers.Function.bind(config.callback, config.scope) :
config.callback;
// optional success callback
var success;
if(config.success) {
success = (config.scope) ?
OpenLayers.Function.bind(config.success, config.scope) :
config.success;
}
// optional failure callback
var failure;
if(config.failure) {
failure = (config.scope) ?
OpenLayers.Function.bind(config.failure, config.scope) :
config.failure;
}
if (OpenLayers.Util.createUrlObject(config.url).protocol == "file:" &&
request.responseText) {
request.status = 200;
}
complete(request);
if (!request.status || (request.status >= 200 && request.status < 300)) {
this.events.triggerEvent("success", options);
if(success) {
success(request);
}
}
if(request.status && (request.status < 200 || request.status >= 300)) {
this.events.triggerEvent("failure", options);
if(failure) {
failure(request);
}
}
},
/**
* APIMethod: GET
* Send an HTTP GET request. Additional configuration properties are
* documented in the <issue> method, with the method property set
* to GET.
*
* Parameters:
* config - {Object} Object with properties for configuring the request.
* See the <issue> method for documentation of allowed properties.
* This object is modified and should not be reused.
*
* Returns:
* {XMLHttpRequest} Request object.
*/
GET: function(config) {
config = OpenLayers.Util.extend(config, {method: "GET"});
return OpenLayers.Request.issue(config);
},
/**
* APIMethod: POST
* Send a POST request. Additional configuration properties are
* documented in the <issue> method, with the method property set
* to POST and "Content-Type" header set to "application/xml".
*
* Parameters:
* config - {Object} Object with properties for configuring the request.
* See the <issue> method for documentation of allowed properties. The
* default "Content-Type" header will be set to "application-xml" if
* none is provided. This object is modified and should not be reused.
*
* Returns:
* {XMLHttpRequest} Request object.
*/
POST: function(config) {
config = OpenLayers.Util.extend(config, {method: "POST"});
// set content type to application/xml if it isn't already set
config.headers = config.headers ? config.headers : {};
if(!("CONTENT-TYPE" in OpenLayers.Util.upperCaseObject(config.headers))) {
config.headers["Content-Type"] = "application/xml";
}
return OpenLayers.Request.issue(config);
},
/**
* APIMethod: PUT
* Send an HTTP PUT request. Additional configuration properties are
* documented in the <issue> method, with the method property set
* to PUT and "Content-Type" header set to "application/xml".
*
* Parameters:
* config - {Object} Object with properties for configuring the request.
* See the <issue> method for documentation of allowed properties. The
* default "Content-Type" header will be set to "application-xml" if
* none is provided. This object is modified and should not be reused.
*
* Returns:
* {XMLHttpRequest} Request object.
*/
PUT: function(config) {
config = OpenLayers.Util.extend(config, {method: "PUT"});
// set content type to application/xml if it isn't already set
config.headers = config.headers ? config.headers : {};
if(!("CONTENT-TYPE" in OpenLayers.Util.upperCaseObject(config.headers))) {
config.headers["Content-Type"] = "application/xml";
}
return OpenLayers.Request.issue(config);
},
/**
* APIMethod: DELETE
* Send an HTTP DELETE request. Additional configuration properties are
* documented in the <issue> method, with the method property set
* to DELETE.
*
* Parameters:
* config - {Object} Object with properties for configuring the request.
* See the <issue> method for documentation of allowed properties.
* This object is modified and should not be reused.
*
* Returns:
* {XMLHttpRequest} Request object.
*/
DELETE: function(config) {
config = OpenLayers.Util.extend(config, {method: "DELETE"});
return OpenLayers.Request.issue(config);
},
/**
* APIMethod: HEAD
* Send an HTTP HEAD request. Additional configuration properties are
* documented in the <issue> method, with the method property set
* to HEAD.
*
* Parameters:
* config - {Object} Object with properties for configuring the request.
* See the <issue> method for documentation of allowed properties.
* This object is modified and should not be reused.
*
* Returns:
* {XMLHttpRequest} Request object.
*/
HEAD: function(config) {
config = OpenLayers.Util.extend(config, {method: "HEAD"});
return OpenLayers.Request.issue(config);
},
/**
* APIMethod: OPTIONS
* Send an HTTP OPTIONS request. Additional configuration properties are
* documented in the <issue> method, with the method property set
* to OPTIONS.
*
* Parameters:
* config - {Object} Object with properties for configuring the request.
* See the <issue> method for documentation of allowed properties.
* This object is modified and should not be reused.
*
* Returns:
* {XMLHttpRequest} Request object.
*/
OPTIONS: function(config) {
config = OpenLayers.Util.extend(config, {method: "OPTIONS"});
return OpenLayers.Request.issue(config);
}
});
/* ======================================================================
OpenLayers/Request/XMLHttpRequest.js
====================================================================== */
// XMLHttpRequest.js Copyright (C) 2010 Sergey Ilinsky (http://www.ilinsky.com)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @requires OpenLayers/Request.js
*/
(function () {
// Save reference to earlier defined object implementation (if any)
var oXMLHttpRequest = window.XMLHttpRequest;
// Define on browser type
var bGecko = !!window.controllers,
bIE = window.document.all && !window.opera,
bIE7 = bIE && window.navigator.userAgent.match(/MSIE 7.0/);
// Enables "XMLHttpRequest()" call next to "new XMLHttpReques()"
function fXMLHttpRequest() {
this._object = oXMLHttpRequest && !bIE7 ? new oXMLHttpRequest : new window.ActiveXObject("Microsoft.XMLHTTP");
this._listeners = [];
};
// Constructor
function cXMLHttpRequest() {
return new fXMLHttpRequest;
};
cXMLHttpRequest.prototype = fXMLHttpRequest.prototype;
// BUGFIX: Firefox with Firebug installed would break pages if not executed
if (bGecko && oXMLHttpRequest.wrapped)
cXMLHttpRequest.wrapped = oXMLHttpRequest.wrapped;
// Constants
cXMLHttpRequest.UNSENT = 0;
cXMLHttpRequest.OPENED = 1;
cXMLHttpRequest.HEADERS_RECEIVED = 2;
cXMLHttpRequest.LOADING = 3;
cXMLHttpRequest.DONE = 4;
// Public Properties
cXMLHttpRequest.prototype.readyState = cXMLHttpRequest.UNSENT;
cXMLHttpRequest.prototype.responseText = '';
cXMLHttpRequest.prototype.responseXML = null;
cXMLHttpRequest.prototype.status = 0;
cXMLHttpRequest.prototype.statusText = '';
// Priority proposal
cXMLHttpRequest.prototype.priority = "NORMAL";
// Instance-level Events Handlers
cXMLHttpRequest.prototype.onreadystatechange = null;
// Class-level Events Handlers
cXMLHttpRequest.onreadystatechange = null;
cXMLHttpRequest.onopen = null;
cXMLHttpRequest.onsend = null;
cXMLHttpRequest.onabort = null;
// Public Methods
cXMLHttpRequest.prototype.open = function(sMethod, sUrl, bAsync, sUser, sPassword) {
// Delete headers, required when object is reused
delete this._headers;
// When bAsync parameter value is omitted, use true as default
if (arguments.length < 3)
bAsync = true;
// Save async parameter for fixing Gecko bug with missing readystatechange in synchronous requests
this._async = bAsync;
// Set the onreadystatechange handler
var oRequest = this,
nState = this.readyState,
fOnUnload;
// BUGFIX: IE - memory leak on page unload (inter-page leak)
if (bIE && bAsync) {
fOnUnload = function() {
if (nState != cXMLHttpRequest.DONE) {
fCleanTransport(oRequest);
// Safe to abort here since onreadystatechange handler removed
oRequest.abort();
}
};
window.attachEvent("onunload", fOnUnload);
}
// Add method sniffer
if (cXMLHttpRequest.onopen)
cXMLHttpRequest.onopen.apply(this, arguments);
if (arguments.length > 4)
this._object.open(sMethod, sUrl, bAsync, sUser, sPassword);
else
if (arguments.length > 3)
this._object.open(sMethod, sUrl, bAsync, sUser);
else
this._object.open(sMethod, sUrl, bAsync);
this.readyState = cXMLHttpRequest.OPENED;
fReadyStateChange(this);
this._object.onreadystatechange = function() {
if (bGecko && !bAsync)
return;
// Synchronize state
oRequest.readyState = oRequest._object.readyState;
//
fSynchronizeValues(oRequest);
// BUGFIX: Firefox fires unnecessary DONE when aborting
if (oRequest._aborted) {
// Reset readyState to UNSENT
oRequest.readyState = cXMLHttpRequest.UNSENT;
// Return now
return;
}
if (oRequest.readyState == cXMLHttpRequest.DONE) {
// Free up queue
delete oRequest._data;
/* if (bAsync)
fQueue_remove(oRequest);*/
//
fCleanTransport(oRequest);
// Uncomment this block if you need a fix for IE cache
/*
// BUGFIX: IE - cache issue
if (!oRequest._object.getResponseHeader("Date")) {
// Save object to cache
oRequest._cached = oRequest._object;
// Instantiate a new transport object
cXMLHttpRequest.call(oRequest);
// Re-send request
if (sUser) {
if (sPassword)
oRequest._object.open(sMethod, sUrl, bAsync, sUser, sPassword);
else
oRequest._object.open(sMethod, sUrl, bAsync, sUser);
}
else
oRequest._object.open(sMethod, sUrl, bAsync);
oRequest._object.setRequestHeader("If-Modified-Since", oRequest._cached.getResponseHeader("Last-Modified") || new window.Date(0));
// Copy headers set
if (oRequest._headers)
for (var sHeader in oRequest._headers)
if (typeof oRequest._headers[sHeader] == "string") // Some frameworks prototype objects with functions
oRequest._object.setRequestHeader(sHeader, oRequest._headers[sHeader]);
oRequest._object.onreadystatechange = function() {
// Synchronize state
oRequest.readyState = oRequest._object.readyState;
if (oRequest._aborted) {
//
oRequest.readyState = cXMLHttpRequest.UNSENT;
// Return
return;
}
if (oRequest.readyState == cXMLHttpRequest.DONE) {
// Clean Object
fCleanTransport(oRequest);
// get cached request
if (oRequest.status == 304)
oRequest._object = oRequest._cached;
//
delete oRequest._cached;
//
fSynchronizeValues(oRequest);
//
fReadyStateChange(oRequest);
// BUGFIX: IE - memory leak in interrupted
if (bIE && bAsync)
window.detachEvent("onunload", fOnUnload);
}
};
oRequest._object.send(null);
// Return now - wait until re-sent request is finished
return;
};
*/
// BUGFIX: IE - memory leak in interrupted
if (bIE && bAsync)
window.detachEvent("onunload", fOnUnload);
}
// BUGFIX: Some browsers (Internet Explorer, Gecko) fire OPEN readystate twice
if (nState != oRequest.readyState)
fReadyStateChange(oRequest);
nState = oRequest.readyState;
}
};
function fXMLHttpRequest_send(oRequest) {
oRequest._object.send(oRequest._data);
// BUGFIX: Gecko - missing readystatechange calls in synchronous requests
if (bGecko && !oRequest._async) {
oRequest.readyState = cXMLHttpRequest.OPENED;
// Synchronize state
fSynchronizeValues(oRequest);
// Simulate missing states
while (oRequest.readyState < cXMLHttpRequest.DONE) {
oRequest.readyState++;
fReadyStateChange(oRequest);
// Check if we are aborted
if (oRequest._aborted)
return;
}
}
};
cXMLHttpRequest.prototype.send = function(vData) {
// Add method sniffer
if (cXMLHttpRequest.onsend)
cXMLHttpRequest.onsend.apply(this, arguments);
if (!arguments.length)
vData = null;
// BUGFIX: Safari - fails sending documents created/modified dynamically, so an explicit serialization required
// BUGFIX: IE - rewrites any custom mime-type to "text/xml" in case an XMLNode is sent
// BUGFIX: Gecko - fails sending Element (this is up to the implementation either to standard)
if (vData && vData.nodeType) {
vData = window.XMLSerializer ? new window.XMLSerializer().serializeToString(vData) : vData.xml;
if (!this._headers["Content-Type"])
this._object.setRequestHeader("Content-Type", "application/xml");
}
this._data = vData;
/*
// Add to queue
if (this._async)
fQueue_add(this);
else*/
fXMLHttpRequest_send(this);
};
cXMLHttpRequest.prototype.abort = function() {
// Add method sniffer
if (cXMLHttpRequest.onabort)
cXMLHttpRequest.onabort.apply(this, arguments);
// BUGFIX: Gecko - unnecessary DONE when aborting
if (this.readyState > cXMLHttpRequest.UNSENT)
this._aborted = true;
this._object.abort();
// BUGFIX: IE - memory leak
fCleanTransport(this);
this.readyState = cXMLHttpRequest.UNSENT;
delete this._data;
/* if (this._async)
fQueue_remove(this);*/
};
cXMLHttpRequest.prototype.getAllResponseHeaders = function() {
return this._object.getAllResponseHeaders();
};
cXMLHttpRequest.prototype.getResponseHeader = function(sName) {
return this._object.getResponseHeader(sName);
};
cXMLHttpRequest.prototype.setRequestHeader = function(sName, sValue) {
// BUGFIX: IE - cache issue
if (!this._headers)
this._headers = {};
this._headers[sName] = sValue;
return this._object.setRequestHeader(sName, sValue);
};
// EventTarget interface implementation
cXMLHttpRequest.prototype.addEventListener = function(sName, fHandler, bUseCapture) {
for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++)
if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture)
return;
// Add listener
this._listeners.push([sName, fHandler, bUseCapture]);
};
cXMLHttpRequest.prototype.removeEventListener = function(sName, fHandler, bUseCapture) {
for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++)
if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture)
break;
// Remove listener
if (oListener)
this._listeners.splice(nIndex, 1);
};
cXMLHttpRequest.prototype.dispatchEvent = function(oEvent) {
var oEventPseudo = {
'type': oEvent.type,
'target': this,
'currentTarget':this,
'eventPhase': 2,
'bubbles': oEvent.bubbles,
'cancelable': oEvent.cancelable,
'timeStamp': oEvent.timeStamp,
'stopPropagation': function() {}, // There is no flow
'preventDefault': function() {}, // There is no default action
'initEvent': function() {} // Original event object should be initialized
};
// Execute onreadystatechange
if (oEventPseudo.type == "readystatechange" && this.onreadystatechange)
(this.onreadystatechange.handleEvent || this.onreadystatechange).apply(this, [oEventPseudo]);
// Execute listeners
for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++)
if (oListener[0] == oEventPseudo.type && !oListener[2])
(oListener[1].handleEvent || oListener[1]).apply(this, [oEventPseudo]);
};
//
cXMLHttpRequest.prototype.toString = function() {
return '[' + "object" + ' ' + "XMLHttpRequest" + ']';
};
cXMLHttpRequest.toString = function() {
return '[' + "XMLHttpRequest" + ']';
};
// Helper function
function fReadyStateChange(oRequest) {
// Sniffing code
if (cXMLHttpRequest.onreadystatechange)
cXMLHttpRequest.onreadystatechange.apply(oRequest);
// Fake event
oRequest.dispatchEvent({
'type': "readystatechange",
'bubbles': false,
'cancelable': false,
'timeStamp': new Date + 0
});
};
function fGetDocument(oRequest) {
var oDocument = oRequest.responseXML,
sResponse = oRequest.responseText;
// Try parsing responseText
if (bIE && sResponse && oDocument && !oDocument.documentElement && oRequest.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/)) {
oDocument = new window.ActiveXObject("Microsoft.XMLDOM");
oDocument.async = false;
oDocument.validateOnParse = false;
oDocument.loadXML(sResponse);
}
// Check if there is no error in document
if (oDocument)
if ((bIE && oDocument.parseError != 0) || !oDocument.documentElement || (oDocument.documentElement && oDocument.documentElement.tagName == "parsererror"))
return null;
return oDocument;
};
function fSynchronizeValues(oRequest) {
try { oRequest.responseText = oRequest._object.responseText; } catch (e) {}
try { oRequest.responseXML = fGetDocument(oRequest._object); } catch (e) {}
try { oRequest.status = oRequest._object.status; } catch (e) {}
try { oRequest.statusText = oRequest._object.statusText; } catch (e) {}
};
function fCleanTransport(oRequest) {
// BUGFIX: IE - memory leak (on-page leak)
oRequest._object.onreadystatechange = new window.Function;
};
/*
// Queue manager
var oQueuePending = {"CRITICAL":[],"HIGH":[],"NORMAL":[],"LOW":[],"LOWEST":[]},
aQueueRunning = [];
function fQueue_add(oRequest) {
oQueuePending[oRequest.priority in oQueuePending ? oRequest.priority : "NORMAL"].push(oRequest);
//
setTimeout(fQueue_process);
};
function fQueue_remove(oRequest) {
for (var nIndex = 0, bFound = false; nIndex < aQueueRunning.length; nIndex++)
if (bFound)
aQueueRunning[nIndex - 1] = aQueueRunning[nIndex];
else
if (aQueueRunning[nIndex] == oRequest)
bFound = true;
if (bFound)
aQueueRunning.length--;
//
setTimeout(fQueue_process);
};
function fQueue_process() {
if (aQueueRunning.length < 6) {
for (var sPriority in oQueuePending) {
if (oQueuePending[sPriority].length) {
var oRequest = oQueuePending[sPriority][0];
oQueuePending[sPriority] = oQueuePending[sPriority].slice(1);
//
aQueueRunning.push(oRequest);
// Send request
fXMLHttpRequest_send(oRequest);
break;
}
}
}
};
*/
// Internet Explorer 5.0 (missing apply)
if (!window.Function.prototype.apply) {
window.Function.prototype.apply = function(oRequest, oArguments) {
if (!oArguments)
oArguments = [];
oRequest.__func = this;
oRequest.__func(oArguments[0], oArguments[1], oArguments[2], oArguments[3], oArguments[4]);
delete oRequest.__func;
};
};
// Register new object with window
/**
* Class: OpenLayers.Request.XMLHttpRequest
* Standard-compliant (W3C) cross-browser implementation of the
* XMLHttpRequest object. From
* http://code.google.com/p/xmlhttprequest/.
*/
if (!OpenLayers.Request) {
/**
* This allows for OpenLayers/Request.js to be included
* before or after this script.
*/
OpenLayers.Request = {};
}
OpenLayers.Request.XMLHttpRequest = cXMLHttpRequest;
})();
/* ======================================================================
OpenLayers/Protocol/HTTP.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Protocol.js
* @requires OpenLayers/Request/XMLHttpRequest.js
*/
/**
* if application uses the query string, for example, for BBOX parameters,
* OpenLayers/Format/QueryStringFilter.js should be included in the build config file
*/
/**
* Class: OpenLayers.Protocol.HTTP
* A basic HTTP protocol for vector layers. Create a new instance with the
* <OpenLayers.Protocol.HTTP> constructor.
*
* Inherits from:
* - <OpenLayers.Protocol>
*/
OpenLayers.Protocol.HTTP = OpenLayers.Class(OpenLayers.Protocol, {
/**
* Property: url
* {String} Service URL, read-only, set through the options
* passed to constructor.
*/
url: null,
/**
* Property: headers
* {Object} HTTP request headers, read-only, set through the options
* passed to the constructor,
* Example: {'Content-Type': 'plain/text'}
*/
headers: null,
/**
* Property: params
* {Object} Parameters of GET requests, read-only, set through the options
* passed to the constructor,
* Example: {'bbox': '5,5,5,5'}
*/
params: null,
/**
* Property: callback
* {Object} Function to be called when the <read>, <create>,
* <update>, <delete> or <commit> operation completes, read-only,
* set through the options passed to the constructor.
*/
callback: null,
/**
* Property: scope
* {Object} Callback execution scope, read-only, set through the
* options passed to the constructor.
*/
scope: null,
/**
* APIProperty: readWithPOST
* {Boolean} true if read operations are done with POST requests
* instead of GET, defaults to false.
*/
readWithPOST: false,
/**
* APIProperty: updateWithPOST
* {Boolean} true if update operations are done with POST requests
* defaults to false.
*/
updateWithPOST: false,
/**
* APIProperty: deleteWithPOST
* {Boolean} true if delete operations are done with POST requests
* defaults to false.
* if true, POST data is set to output of format.write().
*/
deleteWithPOST: false,
/**
* Property: wildcarded.
* {Boolean} If true percent signs are added around values
* read from LIKE filters, for example if the protocol
* read method is passed a LIKE filter whose property
* is "foo" and whose value is "bar" the string
* "foo__ilike=%bar%" will be sent in the query string;
* defaults to false.
*/
wildcarded: false,
/**
* APIProperty: srsInBBOX
* {Boolean} Include the SRS identifier in BBOX query string parameter.
* Default is false. If true and the layer has a projection object set,
* any BBOX filter will be serialized with a fifth item identifying the
* projection. E.g. bbox=-1000,-1000,1000,1000,EPSG:900913
*/
srsInBBOX: false,
/**
* Constructor: OpenLayers.Protocol.HTTP
* A class for giving layers generic HTTP protocol.
*
* Parameters:
* options - {Object} Optional object whose properties will be set on the
* instance.
*
* Valid options include:
* url - {String}
* headers - {Object}
* params - {Object} URL parameters for GET requests
* format - {<OpenLayers.Format>}
* callback - {Function}
* scope - {Object}
*/
initialize: function(options) {
options = options || {};
this.params = {};
this.headers = {};
OpenLayers.Protocol.prototype.initialize.apply(this, arguments);
if (!this.filterToParams && OpenLayers.Format.QueryStringFilter) {
var format = new OpenLayers.Format.QueryStringFilter({
wildcarded: this.wildcarded,
srsInBBOX: this.srsInBBOX
});
this.filterToParams = function(filter, params) {
return format.write(filter, params);
};
}
},
/**
* APIMethod: destroy
* Clean up the protocol.
*/
destroy: function() {
this.params = null;
this.headers = null;
OpenLayers.Protocol.prototype.destroy.apply(this);
},
/**
* APIMethod: filterToParams
* Optional method to translate an <OpenLayers.Filter> object into an object
* that can be serialized as request query string provided. If a custom
* method is not provided, the filter will be serialized using the
* <OpenLayers.Format.QueryStringFilter> class.
*
* Parameters:
* filter - {<OpenLayers.Filter>} filter to convert.
* params - {Object} The parameters object.
*
* Returns:
* {Object} The resulting parameters object.
*/
/**
* APIMethod: read
* Construct a request for reading new features.
*
* Parameters:
* options - {Object} Optional object for configuring the request.
* This object is modified and should not be reused.
*
* Valid options:
* url - {String} Url for the request.
* params - {Object} Parameters to get serialized as a query string.
* headers - {Object} Headers to be set on the request.
* filter - {<OpenLayers.Filter>} Filter to get serialized as a
* query string.
* readWithPOST - {Boolean} If the request should be done with POST.
*
* Returns:
* {<OpenLayers.Protocol.Response>} A response object, whose "priv" property
* references the HTTP request, this object is also passed to the
* callback function when the request completes, its "features" property
* is then populated with the features received from the server.
*/
read: function(options) {
OpenLayers.Protocol.prototype.read.apply(this, arguments);
options = options || {};
options.params = OpenLayers.Util.applyDefaults(
options.params, this.options.params);
options = OpenLayers.Util.applyDefaults(options, this.options);
if (options.filter && this.filterToParams) {
options.params = this.filterToParams(
options.filter, options.params
);
}
var readWithPOST = (options.readWithPOST !== undefined) ?
options.readWithPOST : this.readWithPOST;
var resp = new OpenLayers.Protocol.Response({requestType: "read"});
if(readWithPOST) {
var headers = options.headers || {};
headers["Content-Type"] = "application/x-www-form-urlencoded";
resp.priv = OpenLayers.Request.POST({
url: options.url,
callback: this.createCallback(this.handleRead, resp, options),
data: OpenLayers.Util.getParameterString(options.params),
headers: headers
});
} else {
resp.priv = OpenLayers.Request.GET({
url: options.url,
callback: this.createCallback(this.handleRead, resp, options),
params: options.params,
headers: options.headers
});
}
return resp;
},
/**
* Method: handleRead
* Individual callbacks are created for read, create and update, should
* a subclass need to override each one separately.
*
* Parameters:
* resp - {<OpenLayers.Protocol.Response>} The response object to pass to
* the user callback.
* options - {Object} The user options passed to the read call.
*/
handleRead: function(resp, options) {
this.handleResponse(resp, options);
},
/**
* APIMethod: create
* Construct a request for writing newly created features.
*
* Parameters:
* features - {Array({<OpenLayers.Feature.Vector>})} or
* {<OpenLayers.Feature.Vector>}
* options - {Object} Optional object for configuring the request.
* This object is modified and should not be reused.
*
* Returns:
* {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
* object, whose "priv" property references the HTTP request, this
* object is also passed to the callback function when the request
* completes, its "features" property is then populated with the
* the features received from the server.
*/
create: function(features, options) {
options = OpenLayers.Util.applyDefaults(options, this.options);
var resp = new OpenLayers.Protocol.Response({
reqFeatures: features,
requestType: "create"
});
resp.priv = OpenLayers.Request.POST({
url: options.url,
callback: this.createCallback(this.handleCreate, resp, options),
headers: options.headers,
data: this.format.write(features)
});
return resp;
},
/**
* Method: handleCreate
* Called the the request issued by <create> is complete. May be overridden
* by subclasses.
*
* Parameters:
* resp - {<OpenLayers.Protocol.Response>} The response object to pass to
* any user callback.
* options - {Object} The user options passed to the create call.
*/
handleCreate: function(resp, options) {
this.handleResponse(resp, options);
},
/**
* APIMethod: update
* Construct a request updating modified feature.
*
* Parameters:
* feature - {<OpenLayers.Feature.Vector>}
* options - {Object} Optional object for configuring the request.
* This object is modified and should not be reused.
*
* Returns:
* {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
* object, whose "priv" property references the HTTP request, this
* object is also passed to the callback function when the request
* completes, its "features" property is then populated with the
* the feature received from the server.
*/
update: function(feature, options) {
options = options || {};
var url = options.url ||
feature.url ||
this.options.url + "/" + feature.fid;
options = OpenLayers.Util.applyDefaults(options, this.options);
var resp = new OpenLayers.Protocol.Response({
reqFeatures: feature,
requestType: "update"
});
var method = this.updateWithPOST ? "POST" : "PUT";
resp.priv = OpenLayers.Request[method]({
url: url,
callback: this.createCallback(this.handleUpdate, resp, options),
headers: options.headers,
data: this.format.write(feature)
});
return resp;
},
/**
* Method: handleUpdate
* Called the the request issued by <update> is complete. May be overridden
* by subclasses.
*
* Parameters:
* resp - {<OpenLayers.Protocol.Response>} The response object to pass to
* any user callback.
* options - {Object} The user options passed to the update call.
*/
handleUpdate: function(resp, options) {
this.handleResponse(resp, options);
},
/**
* APIMethod: delete
* Construct a request deleting a removed feature.
*
* Parameters:
* feature - {<OpenLayers.Feature.Vector>}
* options - {Object} Optional object for configuring the request.
* This object is modified and should not be reused.
*
* Returns:
* {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
* object, whose "priv" property references the HTTP request, this
* object is also passed to the callback function when the request
* completes.
*/
"delete": function(feature, options) {
options = options || {};
var url = options.url ||
feature.url ||
this.options.url + "/" + feature.fid;
options = OpenLayers.Util.applyDefaults(options, this.options);
var resp = new OpenLayers.Protocol.Response({
reqFeatures: feature,
requestType: "delete"
});
var method = this.deleteWithPOST ? "POST" : "DELETE";
var requestOptions = {
url: url,
callback: this.createCallback(this.handleDelete, resp, options),
headers: options.headers
};
if (this.deleteWithPOST) {
requestOptions.data = this.format.write(feature);
}
resp.priv = OpenLayers.Request[method](requestOptions);
return resp;
},
/**
* Method: handleDelete
* Called the the request issued by <delete> is complete. May be overridden
* by subclasses.
*
* Parameters:
* resp - {<OpenLayers.Protocol.Response>} The response object to pass to
* any user callback.
* options - {Object} The user options passed to the delete call.
*/
handleDelete: function(resp, options) {
this.handleResponse(resp, options);
},
/**
* Method: handleResponse
* Called by CRUD specific handlers.
*
* Parameters:
* resp - {<OpenLayers.Protocol.Response>} The response object to pass to
* any user callback.
* options - {Object} The user options passed to the create, read, update,
* or delete call.
*/
handleResponse: function(resp, options) {
var request = resp.priv;
if(options.callback) {
if(request.status >= 200 && request.status < 300) {
// success
if(resp.requestType != "delete") {
resp.features = this.parseFeatures(request);
}
resp.code = OpenLayers.Protocol.Response.SUCCESS;
} else {
// failure
resp.code = OpenLayers.Protocol.Response.FAILURE;
}
options.callback.call(options.scope, resp);
}
},
/**
* Method: parseFeatures
* Read HTTP response body and return features.
*
* Parameters:
* request - {XMLHttpRequest} The request object
*
* Returns:
* {Array({<OpenLayers.Feature.Vector>})} or
* {<OpenLayers.Feature.Vector>} Array of features or a single feature.
*/
parseFeatures: function(request) {
var doc = request.responseXML;
if (!doc || !doc.documentElement) {
doc = request.responseText;
}
if (!doc || doc.length <= 0) {
return null;
}
return this.format.read(doc);
},
/**
* APIMethod: commit
* Iterate over each feature and take action based on the feature state.
* Possible actions are create, update and delete.
*
* Parameters:
* features - {Array({<OpenLayers.Feature.Vector>})}
* options - {Object} Optional object for setting up intermediate commit
* callbacks.
*
* Valid options:
* create - {Object} Optional object to be passed to the <create> method.
* update - {Object} Optional object to be passed to the <update> method.
* delete - {Object} Optional object to be passed to the <delete> method.
* callback - {Function} Optional function to be called when the commit
* is complete.
* scope - {Object} Optional object to be set as the scope of the callback.
*
* Returns:
* {Array(<OpenLayers.Protocol.Response>)} An array of response objects,
* one per request made to the server, each object's "priv" property
* references the corresponding HTTP request.
*/
commit: function(features, options) {
options = OpenLayers.Util.applyDefaults(options, this.options);
var resp = [], nResponses = 0;
// Divide up features before issuing any requests. This properly
// counts requests in the event that any responses come in before
// all requests have been issued.
var types = {};
types[OpenLayers.State.INSERT] = [];
types[OpenLayers.State.UPDATE] = [];
types[OpenLayers.State.DELETE] = [];
var feature, list, requestFeatures = [];
for(var i=0, len=features.length; i<len; ++i) {
feature = features[i];
list = types[feature.state];
if(list) {
list.push(feature);
requestFeatures.push(feature);
}
}
// tally up number of requests
var nRequests = (types[OpenLayers.State.INSERT].length > 0 ? 1 : 0) +
types[OpenLayers.State.UPDATE].length +
types[OpenLayers.State.DELETE].length;
// This response will be sent to the final callback after all the others
// have been fired.
var success = true;
var finalResponse = new OpenLayers.Protocol.Response({
reqFeatures: requestFeatures
});
function insertCallback(response) {
var len = response.features ? response.features.length : 0;
var fids = new Array(len);
for(var i=0; i<len; ++i) {
fids[i] = response.features[i].fid;
}
finalResponse.insertIds = fids;
callback.apply(this, [response]);
}
function callback(response) {
this.callUserCallback(response, options);
success = success && response.success();
nResponses++;
if (nResponses >= nRequests) {
if (options.callback) {
finalResponse.code = success ?
OpenLayers.Protocol.Response.SUCCESS :
OpenLayers.Protocol.Response.FAILURE;
options.callback.apply(options.scope, [finalResponse]);
}
}
}
// start issuing requests
var queue = types[OpenLayers.State.INSERT];
if(queue.length > 0) {
resp.push(this.create(
queue, OpenLayers.Util.applyDefaults(
{callback: insertCallback, scope: this}, options.create
)
));
}
queue = types[OpenLayers.State.UPDATE];
for(var i=queue.length-1; i>=0; --i) {
resp.push(this.update(
queue[i], OpenLayers.Util.applyDefaults(
{callback: callback, scope: this}, options.update
))
);
}
queue = types[OpenLayers.State.DELETE];
for(var i=queue.length-1; i>=0; --i) {
resp.push(this["delete"](
queue[i], OpenLayers.Util.applyDefaults(
{callback: callback, scope: this}, options["delete"]
))
);
}
return resp;
},
/**
* APIMethod: abort
* Abort an ongoing request, the response object passed to
* this method must come from this HTTP protocol (as a result
* of a create, read, update, delete or commit operation).
*
* Parameters:
* response - {<OpenLayers.Protocol.Response>}
*/
abort: function(response) {
if (response) {
response.priv.abort();
}
},
/**
* Method: callUserCallback
* This method is used from within the commit method each time an
* an HTTP response is received from the server, it is responsible
* for calling the user-supplied callbacks.
*
* Parameters:
* resp - {<OpenLayers.Protocol.Response>}
* options - {Object} The map of options passed to the commit call.
*/
callUserCallback: function(resp, options) {
var opt = options[resp.requestType];
if(opt && opt.callback) {
opt.callback.call(opt.scope, resp);
}
},
CLASS_NAME: "OpenLayers.Protocol.HTTP"
});
/* ======================================================================
OpenLayers/Protocol/WFS.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Protocol.js
*/
/**
* Class: OpenLayers.Protocol.WFS
* Used to create a versioned WFS protocol. Default version is 1.0.0.
*
* Returns:
* {<OpenLayers.Protocol>} A WFS protocol of the given version.
*
* Example:
* (code)
* var protocol = new OpenLayers.Protocol.WFS({
* version: "1.1.0",
* url: "http://demo.opengeo.org/geoserver/wfs",
* featureType: "tasmania_roads",
* featureNS: "http://www.openplans.org/topp",
* geometryName: "the_geom"
* });
* (end)
*
* See the protocols for specific WFS versions for more detail.
*/
OpenLayers.Protocol.WFS = function(options) {
options = OpenLayers.Util.applyDefaults(
options, OpenLayers.Protocol.WFS.DEFAULTS
);
var cls = OpenLayers.Protocol.WFS["v"+options.version.replace(/\./g, "_")];
if(!cls) {
throw "Unsupported WFS version: " + options.version;
}
return new cls(options);
};
/**
* Function: fromWMSLayer
* Convenience function to create a WFS protocol from a WMS layer. This makes
* the assumption that a WFS requests can be issued at the same URL as
* WMS requests and that a WFS featureType exists with the same name as the
* WMS layer.
*
* This function is designed to auto-configure <url>, <featureType>,
* <featurePrefix> and <srsName> for WFS <version> 1.1.0. Note that
* srsName matching with the WMS layer will not work with WFS 1.0.0.
*
* Parameters:
* layer - {<OpenLayers.Layer.WMS>} WMS layer that has a matching WFS
* FeatureType at the same server url with the same typename.
* options - {Object} Default properties to be set on the protocol.
*
* Returns:
* {<OpenLayers.Protocol.WFS>}
*/
OpenLayers.Protocol.WFS.fromWMSLayer = function(layer, options) {
var typeName, featurePrefix;
var param = layer.params["LAYERS"];
var parts = (OpenLayers.Util.isArray(param) ? param[0] : param).split(":");
if(parts.length > 1) {
featurePrefix = parts[0];
}
typeName = parts.pop();
var protocolOptions = {
url: layer.url,
featureType: typeName,
featurePrefix: featurePrefix,
srsName: layer.projection && layer.projection.getCode() ||
layer.map && layer.map.getProjectionObject().getCode(),
version: "1.1.0"
};
return new OpenLayers.Protocol.WFS(OpenLayers.Util.applyDefaults(
options, protocolOptions
));
};
/**
* Constant: OpenLayers.Protocol.WFS.DEFAULTS
*/
OpenLayers.Protocol.WFS.DEFAULTS = {
"version": "1.0.0"
};
/* ======================================================================
OpenLayers/Protocol/WFS/v1.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Protocol/WFS.js
*/
/**
* Class: OpenLayers.Protocol.WFS.v1
* Abstract class for for v1.0.0 and v1.1.0 protocol.
*
* Inherits from:
* - <OpenLayers.Protocol>
*/
OpenLayers.Protocol.WFS.v1 = OpenLayers.Class(OpenLayers.Protocol, {
/**
* Property: version
* {String} WFS version number.
*/
version: null,
/**
* Property: srsName
* {String} Name of spatial reference system. Default is "EPSG:4326".
*/
srsName: "EPSG:4326",
/**
* Property: featureType
* {String} Local feature typeName.
*/
featureType: null,
/**
* Property: featureNS
* {String} Feature namespace.
*/
featureNS: null,
/**
* Property: geometryName
* {String} Name of the geometry attribute for features. Default is
* "the_geom" for WFS <version> 1.0, and null for higher versions.
*/
geometryName: "the_geom",
/**
* Property: maxFeatures
* {Integer} Optional maximum number of features to retrieve.
*/
/**
* Property: schema
* {String} Optional schema location that will be included in the
* schemaLocation attribute value. Note that the feature type schema
* is required for a strict XML validator (on transactions with an
* insert for example), but is *not* required by the WFS specification
* (since the server is supposed to know about feature type schemas).
*/
schema: null,
/**
* Property: featurePrefix
* {String} Namespace alias for feature type. Default is "feature".
*/
featurePrefix: "feature",
/**
* Property: formatOptions
* {Object} Optional options for the format. If a format is not provided,
* this property can be used to extend the default format options.
*/
formatOptions: null,
/**
* Property: readFormat
* {<OpenLayers.Format>} For WFS requests it is possible to get a
* different output format than GML. In that case, we cannot parse
* the response with the default format (WFST) and we need a different
* format for reading.
*/
readFormat: null,
/**
* Property: readOptions
* {Object} Optional object to pass to format's read.
*/
readOptions: null,
/**
* Constructor: OpenLayers.Protocol.WFS
* A class for giving layers WFS protocol.
*
* Parameters:
* options - {Object} Optional object whose properties will be set on the
* instance.
*
* Valid options properties:
* url - {String} URL to send requests to (required).
* featureType - {String} Local (without prefix) feature typeName (required).
* featureNS - {String} Feature namespace (required, but can be autodetected
* during the first query if GML is used as readFormat and
* featurePrefix is provided and matches the prefix used by the server
* for this featureType).
* featurePrefix - {String} Feature namespace alias (optional - only used
* for writing if featureNS is provided). Default is 'feature'.
* geometryName - {String} Name of geometry attribute. The default is
* 'the_geom' for WFS <version> 1.0, and null for higher versions. If
* null, it will be set to the name of the first geometry found in the
* first read operation.
* multi - {Boolean} If set to true, geometries will be casted to Multi
* geometries before they are written in a transaction. No casting will
* be done when reading features.
*/
initialize: function(options) {
OpenLayers.Protocol.prototype.initialize.apply(this, [options]);
if(!options.format) {
this.format = OpenLayers.Format.WFST(OpenLayers.Util.extend({
version: this.version,
featureType: this.featureType,
featureNS: this.featureNS,
featurePrefix: this.featurePrefix,
geometryName: this.geometryName,
srsName: this.srsName,
schema: this.schema
}, this.formatOptions));
}
if (!options.geometryName && parseFloat(this.format.version) > 1.0) {
this.setGeometryName(null);
}
},
/**
* APIMethod: destroy
* Clean up the protocol.
*/
destroy: function() {
if(this.options && !this.options.format) {
this.format.destroy();
}
this.format = null;
OpenLayers.Protocol.prototype.destroy.apply(this);
},
/**
* APIMethod: read
* Construct a request for reading new features. Since WFS splits the
* basic CRUD operations into GetFeature requests (for read) and
* Transactions (for all others), this method does not make use of the
* format's read method (that is only about reading transaction
* responses).
*
* Parameters:
* options - {Object} Options for the read operation, in addition to the
* options set on the instance (options set here will take precedence).
*
* To use a configured protocol to get e.g. a WFS hit count, applications
* could do the following:
*
* (code)
* protocol.read({
* readOptions: {output: "object"},
* resultType: "hits",
* maxFeatures: null,
* callback: function(resp) {
* // process resp.numberOfFeatures here
* }
* });
* (end)
*
* To use a configured protocol to use WFS paging (if supported by the
* server), applications could do the following:
*
* (code)
* protocol.read({
* startIndex: 0,
* count: 50
* });
* (end)
*
* To limit the attributes returned by the GetFeature request, applications
* can use the propertyNames option to specify the properties to include in
* the response:
*
* (code)
* protocol.read({
* propertyNames: ["DURATION", "INTENSITY"]
* });
* (end)
*/
read: function(options) {
OpenLayers.Protocol.prototype.read.apply(this, arguments);
options = OpenLayers.Util.extend({}, options);
OpenLayers.Util.applyDefaults(options, this.options || {});
var response = new OpenLayers.Protocol.Response({requestType: "read"});
var data = OpenLayers.Format.XML.prototype.write.apply(
this.format, [this.format.writeNode("wfs:GetFeature", options)]
);
response.priv = OpenLayers.Request.POST({
url: options.url,
callback: this.createCallback(this.handleRead, response, options),
params: options.params,
headers: options.headers,
data: data
});
return response;
},
/**
* APIMethod: setFeatureType
* Change the feature type on the fly.
*
* Parameters:
* featureType - {String} Local (without prefix) feature typeName.
*/
setFeatureType: function(featureType) {
this.featureType = featureType;
this.format.featureType = featureType;
},
/**
* APIMethod: setGeometryName
* Sets the geometryName option after instantiation.
*
* Parameters:
* geometryName - {String} Name of geometry attribute.
*/
setGeometryName: function(geometryName) {
this.geometryName = geometryName;
this.format.geometryName = geometryName;
},
/**
* Method: handleRead
* Deal with response from the read request.
*
* Parameters:
* response - {<OpenLayers.Protocol.Response>} The response object to pass
* to the user callback.
* options - {Object} The user options passed to the read call.
*/
handleRead: function(response, options) {
options = OpenLayers.Util.extend({}, options);
OpenLayers.Util.applyDefaults(options, this.options);
if(options.callback) {
var request = response.priv;
if(request.status >= 200 && request.status < 300) {
// success
var result = this.parseResponse(request, options.readOptions);
if (result && result.success !== false) {
if (options.readOptions && options.readOptions.output == "object") {
OpenLayers.Util.extend(response, result);
} else {
response.features = result;
}
response.code = OpenLayers.Protocol.Response.SUCCESS;
} else {
// failure (service exception)
response.code = OpenLayers.Protocol.Response.FAILURE;
response.error = result;
}
} else {
// failure
response.code = OpenLayers.Protocol.Response.FAILURE;
}
options.callback.call(options.scope, response);
}
},
/**
* Method: parseResponse
* Read HTTP response body and return features
*
* Parameters:
* request - {XMLHttpRequest} The request object
* options - {Object} Optional object to pass to format's read
*
* Returns:
* {Object} or {Array({<OpenLayers.Feature.Vector>})} or
* {<OpenLayers.Feature.Vector>}
* An object with a features property, an array of features or a single
* feature.
*/
parseResponse: function(request, options) {
var doc = request.responseXML;
if(!doc || !doc.documentElement) {
doc = request.responseText;
}
if(!doc || doc.length <= 0) {
return null;
}
var result = (this.readFormat !== null) ? this.readFormat.read(doc) :
this.format.read(doc, options);
if (!this.featureNS) {
var format = this.readFormat || this.format;
this.featureNS = format.featureNS;
// no need to auto-configure again on subsequent reads
format.autoConfig = false;
if (!this.geometryName) {
this.setGeometryName(format.geometryName);
}
}
return result;
},
/**
* Method: commit
* Given a list of feature, assemble a batch request for update, create,
* and delete transactions. A commit call on the prototype amounts
* to writing a WFS transaction - so the write method on the format
* is used.
*
* Parameters:
* features - {Array(<OpenLayers.Feature.Vector>)}
* options - {Object}
*
* Valid options properties:
* nativeElements - {Array({Object})} Array of objects with information for writing
* out <Native> elements, these objects have vendorId, safeToIgnore and
* value properties. The <Native> element is intended to allow access to
* vendor specific capabilities of any particular web feature server or
* datastore.
*
* Returns:
* {<OpenLayers.Protocol.Response>} A response object with a features
* property containing any insertIds and a priv property referencing
* the XMLHttpRequest object.
*/
commit: function(features, options) {
options = OpenLayers.Util.extend({}, options);
OpenLayers.Util.applyDefaults(options, this.options);
var response = new OpenLayers.Protocol.Response({
requestType: "commit",
reqFeatures: features
});
response.priv = OpenLayers.Request.POST({
url: options.url,
headers: options.headers,
data: this.format.write(features, options),
callback: this.createCallback(this.handleCommit, response, options)
});
return response;
},
/**
* Method: handleCommit
* Called when the commit request returns.
*
* Parameters:
* response - {<OpenLayers.Protocol.Response>} The response object to pass
* to the user callback.
* options - {Object} The user options passed to the commit call.
*/
handleCommit: function(response, options) {
if(options.callback) {
var request = response.priv;
// ensure that we have an xml doc
var data = request.responseXML;
if(!data || !data.documentElement) {
data = request.responseText;
}
var obj = this.format.read(data) || {};
response.insertIds = obj.insertIds || [];
if (obj.success) {
response.code = OpenLayers.Protocol.Response.SUCCESS;
} else {
response.code = OpenLayers.Protocol.Response.FAILURE;
response.error = obj;
}
options.callback.call(options.scope, response);
}
},
/**
* Method: filterDelete
* Send a request that deletes all features by their filter.
*
* Parameters:
* filter - {<OpenLayers.Filter>} filter
*/
filterDelete: function(filter, options) {
options = OpenLayers.Util.extend({}, options);
OpenLayers.Util.applyDefaults(options, this.options);
var response = new OpenLayers.Protocol.Response({
requestType: "commit"
});
var root = this.format.createElementNSPlus("wfs:Transaction", {
attributes: {
service: "WFS",
version: this.version
}
});
var deleteNode = this.format.createElementNSPlus("wfs:Delete", {
attributes: {
typeName: (options.featureNS ? this.featurePrefix + ":" : "") +
options.featureType
}
});
if(options.featureNS) {
deleteNode.setAttribute("xmlns:" + this.featurePrefix, options.featureNS);
}
var filterNode = this.format.writeNode("ogc:Filter", filter);
deleteNode.appendChild(filterNode);
root.appendChild(deleteNode);
var data = OpenLayers.Format.XML.prototype.write.apply(
this.format, [root]
);
return OpenLayers.Request.POST({
url: this.url,
callback : options.callback || function(){},
data: data
});
},
/**
* Method: abort
* Abort an ongoing request, the response object passed to
* this method must come from this protocol (as a result
* of a read, or commit operation).
*
* Parameters:
* response - {<OpenLayers.Protocol.Response>}
*/
abort: function(response) {
if (response) {
response.priv.abort();
}
},
CLASS_NAME: "OpenLayers.Protocol.WFS.v1"
});
/* ======================================================================
OpenLayers/Format.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/BaseTypes/Class.js
* @requires OpenLayers/Util.js
*/
/**
* Class: OpenLayers.Format
* Base class for format reading/writing a variety of formats. Subclasses
* of OpenLayers.Format are expected to have read and write methods.
*/
OpenLayers.Format = OpenLayers.Class({
/**
* Property: options
* {Object} A reference to options passed to the constructor.
*/
options: null,
/**
* APIProperty: externalProjection
* {<OpenLayers.Projection>} When passed a externalProjection and
* internalProjection, the format will reproject the geometries it
* reads or writes. The externalProjection is the projection used by
* the content which is passed into read or which comes out of write.
* In order to reproject, a projection transformation function for the
* specified projections must be available. This support may be
* provided via proj4js or via a custom transformation function. See
* {<OpenLayers.Projection.addTransform>} for more information on
* custom transformations.
*/
externalProjection: null,
/**
* APIProperty: internalProjection
* {<OpenLayers.Projection>} When passed a externalProjection and
* internalProjection, the format will reproject the geometries it
* reads or writes. The internalProjection is the projection used by
* the geometries which are returned by read or which are passed into
* write. In order to reproject, a projection transformation function
* for the specified projections must be available. This support may be
* provided via proj4js or via a custom transformation function. See
* {<OpenLayers.Projection.addTransform>} for more information on
* custom transformations.
*/
internalProjection: null,
/**
* APIProperty: data
* {Object} When <keepData> is true, this is the parsed string sent to
* <read>.
*/
data: null,
/**
* APIProperty: keepData
* {Object} Maintain a reference (<data>) to the most recently read data.
* Default is false.
*/
keepData: false,
/**
* Constructor: OpenLayers.Format
* Instances of this class are not useful. See one of the subclasses.
*
* Parameters:
* options - {Object} An optional object with properties to set on the
* format
*
* Valid options:
* keepData - {Boolean} If true, upon <read>, the data property will be
* set to the parsed object (e.g. the json or xml object).
*
* Returns:
* An instance of OpenLayers.Format
*/
initialize: function(options) {
OpenLayers.Util.extend(this, options);
this.options = options;
},
/**
* APIMethod: destroy
* Clean up.
*/
destroy: function() {
},
/**
* Method: read
* Read data from a string, and return an object whose type depends on the
* subclass.
*
* Parameters:
* data - {string} Data to read/parse.
*
* Returns:
* Depends on the subclass
*/
read: function(data) {
throw new Error('Read not implemented.');
},
/**
* Method: write
* Accept an object, and return a string.
*
* Parameters:
* object - {Object} Object to be serialized
*
* Returns:
* {String} A string representation of the object.
*/
write: function(object) {
throw new Error('Write not implemented.');
},
CLASS_NAME: "OpenLayers.Format"
});
/* ======================================================================
OpenLayers/Format/XML.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Format.js
*/
/**
* Class: OpenLayers.Format.XML
* Read and write XML. For cross-browser XML generation, use methods on an
* instance of the XML format class instead of on <code>document<end>.
* The DOM creation and traversing methods exposed here all mimic the
* W3C XML DOM methods. Create a new parser with the
* <OpenLayers.Format.XML> constructor.
*
* Inherits from:
* - <OpenLayers.Format>
*/
OpenLayers.Format.XML = OpenLayers.Class(OpenLayers.Format, {
/**
* Property: namespaces
* {Object} Mapping of namespace aliases to namespace URIs. Properties
* of this object should not be set individually. Read-only. All
* XML subclasses should have their own namespaces object. Use
* <setNamespace> to add or set a namespace alias after construction.
*/
namespaces: null,
/**
* Property: namespaceAlias
* {Object} Mapping of namespace URI to namespace alias. This object
* is read-only. Use <setNamespace> to add or set a namespace alias.
*/
namespaceAlias: null,
/**
* Property: defaultPrefix
* {String} The default namespace alias for creating element nodes.
*/
defaultPrefix: null,
/**
* Property: readers
* Contains public functions, grouped by namespace prefix, that will
* be applied when a namespaced node is found matching the function
* name. The function will be applied in the scope of this parser
* with two arguments: the node being read and a context object passed
* from the parent.
*/
readers: {},
/**
* Property: writers
* As a compliment to the <readers> property, this structure contains public
* writing functions grouped by namespace alias and named like the
* node names they produce.
*/
writers: {},
/**
* Property: xmldom
* {XMLDom} If this browser uses ActiveX, this will be set to a XMLDOM
* object. It is not intended to be a browser sniffing property.
* Instead, the xmldom property is used instead of <code>document<end>
* where namespaced node creation methods are not supported. In all
* other browsers, this remains null.
*/
xmldom: null,
/**
* Constructor: OpenLayers.Format.XML
* Construct an XML parser. The parser is used to read and write XML.
* Reading XML from a string returns a DOM element. Writing XML from
* a DOM element returns a string.
*
* Parameters:
* options - {Object} Optional object whose properties will be set on
* the object.
*/
initialize: function(options) {
if (OpenLayers.Format.XML.supportActiveX) {
this.xmldom = new ActiveXObject("Microsoft.XMLDOM");
}
OpenLayers.Format.prototype.initialize.apply(this, [options]);
// clone the namespace object and set all namespace aliases
this.namespaces = OpenLayers.Util.extend({}, this.namespaces);
this.namespaceAlias = {};
for(var alias in this.namespaces) {
this.namespaceAlias[this.namespaces[alias]] = alias;
}
},
/**
* APIMethod: destroy
* Clean up.
*/
destroy: function() {
this.xmldom = null;
OpenLayers.Format.prototype.destroy.apply(this, arguments);
},
/**
* Method: setNamespace
* Set a namespace alias and URI for the format.
*
* Parameters:
* alias - {String} The namespace alias (prefix).
* uri - {String} The namespace URI.
*/
setNamespace: function(alias, uri) {
this.namespaces[alias] = uri;
this.namespaceAlias[uri] = alias;
},
/**
* APIMethod: read
* Deserialize a XML string and return a DOM node.
*
* Parameters:
* text - {String} A XML string
* Returns:
* {DOMElement} A DOM node
*/
read: function(text) {
var index = text.indexOf('<');
if(index > 0) {
text = text.substring(index);
}
var node = OpenLayers.Util.Try(
OpenLayers.Function.bind((
function() {
var xmldom;
/**
* Since we want to be able to call this method on the prototype
* itself, this.xmldom may not exist even if in IE.
*/
if (OpenLayers.Format.XML.supportActiveX && !this.xmldom) {
xmldom = new ActiveXObject("Microsoft.XMLDOM");
} else {
xmldom = this.xmldom;
}
xmldom.loadXML(text);
return xmldom;
}
), this),
function() {
return new DOMParser().parseFromString(text, 'text/xml');
},
function() {
var req = new XMLHttpRequest();
req.open("GET", "data:" + "text/xml" +
";charset=utf-8," + encodeURIComponent(text), false);
if(req.overrideMimeType) {
req.overrideMimeType("text/xml");
}
req.send(null);
return req.responseXML;
}
);
if(this.keepData) {
this.data = node;
}
return node;
},
/**
* APIMethod: write
* Serialize a DOM node into a XML string.
*
* Parameters:
* node - {DOMElement} A DOM node.
*
* Returns:
* {String} The XML string representation of the input node.
*/
write: function(node) {
var data;
if(this.xmldom) {
data = node.xml;
} else {
var serializer = new XMLSerializer();
if (node.nodeType == 1) {
// Add nodes to a document before serializing. Everything else
// is serialized as is. This may need more work. See #1218 .
var doc = document.implementation.createDocument("", "", null);
if (doc.importNode) {
node = doc.importNode(node, true);
}
doc.appendChild(node);
data = serializer.serializeToString(doc);
} else {
data = serializer.serializeToString(node);
}
}
return data;
},
/**
* APIMethod: createElementNS
* Create a new element with namespace. This node can be appended to
* another node with the standard node.appendChild method. For
* cross-browser support, this method must be used instead of
* document.createElementNS.
*
* Parameters:
* uri - {String} Namespace URI for the element.
* name - {String} The qualified name of the element (prefix:localname).
*
* Returns:
* {Element} A DOM element with namespace.
*/
createElementNS: function(uri, name) {
var element;
if(this.xmldom) {
if(typeof uri == "string") {
element = this.xmldom.createNode(1, name, uri);
} else {
element = this.xmldom.createNode(1, name, "");
}
} else {
element = document.createElementNS(uri, name);
}
return element;
},
/**
* APIMethod: createDocumentFragment
* Create a document fragment node that can be appended to another node
* created by createElementNS. This will call
* document.createDocumentFragment outside of IE. In IE, the ActiveX
* object's createDocumentFragment method is used.
*
* Returns:
* {Element} A document fragment.
*/
createDocumentFragment: function() {
var element;
if (this.xmldom) {
element = this.xmldom.createDocumentFragment();
} else {
element = document.createDocumentFragment();
}
return element;
},
/**
* APIMethod: createTextNode
* Create a text node. This node can be appended to another node with
* the standard node.appendChild method. For cross-browser support,
* this method must be used instead of document.createTextNode.
*
* Parameters:
* text - {String} The text of the node.
*
* Returns:
* {DOMElement} A DOM text node.
*/
createTextNode: function(text) {
var node;
if (typeof text !== "string") {
text = String(text);
}
if(this.xmldom) {
node = this.xmldom.createTextNode(text);
} else {
node = document.createTextNode(text);
}
return node;
},
/**
* APIMethod: getElementsByTagNameNS
* Get a list of elements on a node given the namespace URI and local name.
* To return all nodes in a given namespace, use '*' for the name
* argument. To return all nodes of a given (local) name, regardless
* of namespace, use '*' for the uri argument.
*
* Parameters:
* node - {Element} Node on which to search for other nodes.
* uri - {String} Namespace URI.
* name - {String} Local name of the tag (without the prefix).
*
* Returns:
* {NodeList} A node list or array of elements.
*/
getElementsByTagNameNS: function(node, uri, name) {
var elements = [];
if(node.getElementsByTagNameNS) {
elements = node.getElementsByTagNameNS(uri, name);
} else {
// brute force method
var allNodes = node.getElementsByTagName("*");
var potentialNode, fullName;
for(var i=0, len=allNodes.length; i<len; ++i) {
potentialNode = allNodes[i];
fullName = (potentialNode.prefix) ?
(potentialNode.prefix + ":" + name) : name;
if((name == "*") || (fullName == potentialNode.nodeName)) {
if((uri == "*") || (uri == potentialNode.namespaceURI)) {
elements.push(potentialNode);
}
}
}
}
return elements;
},
/**
* APIMethod: getAttributeNodeNS
* Get an attribute node given the namespace URI and local name.
*
* Parameters:
* node - {Element} Node on which to search for attribute nodes.
* uri - {String} Namespace URI.
* name - {String} Local name of the attribute (without the prefix).
*
* Returns:
* {DOMElement} An attribute node or null if none found.
*/
getAttributeNodeNS: function(node, uri, name) {
var attributeNode = null;
if(node.getAttributeNodeNS) {
attributeNode = node.getAttributeNodeNS(uri, name);
} else {
var attributes = node.attributes;
var potentialNode, fullName;
for(var i=0, len=attributes.length; i<len; ++i) {
potentialNode = attributes[i];
if(potentialNode.namespaceURI == uri) {
fullName = (potentialNode.prefix) ?
(potentialNode.prefix + ":" + name) : name;
if(fullName == potentialNode.nodeName) {
attributeNode = potentialNode;
break;
}
}
}
}
return attributeNode;
},
/**
* APIMethod: getAttributeNS
* Get an attribute value given the namespace URI and local name.
*
* Parameters:
* node - {Element} Node on which to search for an attribute.
* uri - {String} Namespace URI.
* name - {String} Local name of the attribute (without the prefix).
*
* Returns:
* {String} An attribute value or and empty string if none found.
*/
getAttributeNS: function(node, uri, name) {
var attributeValue = "";
if(node.getAttributeNS) {
attributeValue = node.getAttributeNS(uri, name) || "";
} else {
var attributeNode = this.getAttributeNodeNS(node, uri, name);
if(attributeNode) {
attributeValue = attributeNode.nodeValue;
}
}
return attributeValue;
},
/**
* APIMethod: getChildValue
* Get the textual value of the node if it exists, or return an
* optional default string. Returns an empty string if no first child
* exists and no default value is supplied.
*
* Parameters:
* node - {DOMElement} The element used to look for a first child value.
* def - {String} Optional string to return in the event that no
* first child value exists.
*
* Returns:
* {String} The value of the first child of the given node.
*/
getChildValue: function(node, def) {
var value = def || "";
if(node) {
for(var child=node.firstChild; child; child=child.nextSibling) {
switch(child.nodeType) {
case 3: // text node
case 4: // cdata section
value += child.nodeValue;
}
}
}
return value;
},
/**
* APIMethod: isSimpleContent
* Test if the given node has only simple content (i.e. no child element
* nodes).
*
* Parameters:
* node - {DOMElement} An element node.
*
* Returns:
* {Boolean} The node has no child element nodes (nodes of type 1).
*/
isSimpleContent: function(node) {
var simple = true;
for(var child=node.firstChild; child; child=child.nextSibling) {
if(child.nodeType === 1) {
simple = false;
break;
}
}
return simple;
},
/**
* APIMethod: contentType
* Determine the content type for a given node.
*
* Parameters:
* node - {DOMElement}
*
* Returns:
* {Integer} One of OpenLayers.Format.XML.CONTENT_TYPE.{EMPTY,SIMPLE,COMPLEX,MIXED}
* if the node has no, simple, complex, or mixed content.
*/
contentType: function(node) {
var simple = false,
complex = false;
var type = OpenLayers.Format.XML.CONTENT_TYPE.EMPTY;
for(var child=node.firstChild; child; child=child.nextSibling) {
switch(child.nodeType) {
case 1: // element
complex = true;
break;
case 8: // comment
break;
default:
simple = true;
}
if(complex && simple) {
break;
}
}
if(complex && simple) {
type = OpenLayers.Format.XML.CONTENT_TYPE.MIXED;
} else if(complex) {
return OpenLayers.Format.XML.CONTENT_TYPE.COMPLEX;
} else if(simple) {
return OpenLayers.Format.XML.CONTENT_TYPE.SIMPLE;
}
return type;
},
/**
* APIMethod: hasAttributeNS
* Determine whether a node has a particular attribute matching the given
* name and namespace.
*
* Parameters:
* node - {Element} Node on which to search for an attribute.
* uri - {String} Namespace URI.
* name - {String} Local name of the attribute (without the prefix).
*
* Returns:
* {Boolean} The node has an attribute matching the name and namespace.
*/
hasAttributeNS: function(node, uri, name) {
var found = false;
if(node.hasAttributeNS) {
found = node.hasAttributeNS(uri, name);
} else {
found = !!this.getAttributeNodeNS(node, uri, name);
}
return found;
},
/**
* APIMethod: setAttributeNS
* Adds a new attribute or changes the value of an attribute with the given
* namespace and name.
*
* Parameters:
* node - {Element} Element node on which to set the attribute.
* uri - {String} Namespace URI for the attribute.
* name - {String} Qualified name (prefix:localname) for the attribute.
* value - {String} Attribute value.
*/
setAttributeNS: function(node, uri, name, value) {
if(node.setAttributeNS) {
node.setAttributeNS(uri, name, value);
} else {
if(this.xmldom) {
if(uri) {
var attribute = node.ownerDocument.createNode(
2, name, uri
);
attribute.nodeValue = value;
node.setAttributeNode(attribute);
} else {
node.setAttribute(name, value);
}
} else {
throw "setAttributeNS not implemented";
}
}
},
/**
* Method: createElementNSPlus
* Shorthand for creating namespaced elements with optional attributes and
* child text nodes.
*
* Parameters:
* name - {String} The qualified node name.
* options - {Object} Optional object for node configuration.
*
* Valid options:
* uri - {String} Optional namespace uri for the element - supply a prefix
* instead if the namespace uri is a property of the format's namespace
* object.
* attributes - {Object} Optional attributes to be set using the
* <setAttributes> method.
* value - {String} Optional text to be appended as a text node.
*
* Returns:
* {Element} An element node.
*/
createElementNSPlus: function(name, options) {
options = options || {};
// order of prefix preference
// 1. in the uri option
// 2. in the prefix option
// 3. in the qualified name
// 4. from the defaultPrefix
var uri = options.uri || this.namespaces[options.prefix];
if(!uri) {
var loc = name.indexOf(":");
uri = this.namespaces[name.substring(0, loc)];
}
if(!uri) {
uri = this.namespaces[this.defaultPrefix];
}
var node = this.createElementNS(uri, name);
if(options.attributes) {
this.setAttributes(node, options.attributes);
}
var value = options.value;
if(value != null) {
node.appendChild(this.createTextNode(value));
}
return node;
},
/**
* Method: setAttributes
* Set multiple attributes given key value pairs from an object.
*
* Parameters:
* node - {Element} An element node.
* obj - {Object || Array} An object whose properties represent attribute
* names and values represent attribute values. If an attribute name
* is a qualified name ("prefix:local"), the prefix will be looked up
* in the parsers {namespaces} object. If the prefix is found,
* setAttributeNS will be used instead of setAttribute.
*/
setAttributes: function(node, obj) {
var value, uri;
for(var name in obj) {
if(obj[name] != null && obj[name].toString) {
value = obj[name].toString();
// check for qualified attribute name ("prefix:local")
uri = this.namespaces[name.substring(0, name.indexOf(":"))] || null;
this.setAttributeNS(node, uri, name, value);
}
}
},
/**
* Method: getFirstElementChild
* Implementation of firstElementChild attribute that works on ie7 and ie8.
*
* Parameters:
* node - {DOMElement} The parent node (required).
*
* Returns:
* {DOMElement} The first child element.
*/
getFirstElementChild: function(node) {
if (node.firstElementChild) {
return node.firstElementChild;
}
else {
var child = node.firstChild;
while (child.nodeType != 1 && (child = child.nextSibling)) {}
return child;
}
},
/**
* Method: readNode
* Shorthand for applying one of the named readers given the node
* namespace and local name. Readers take two args (node, obj) and
* generally extend or modify the second.
*
* Parameters:
* node - {DOMElement} The node to be read (required).
* obj - {Object} The object to be modified (optional).
*
* Returns:
* {Object} The input object, modified (or a new one if none was provided).
*/
readNode: function(node, obj) {
if(!obj) {
obj = {};
}
var group = this.readers[node.namespaceURI ? this.namespaceAlias[node.namespaceURI]: this.defaultPrefix];
if(group) {
var local = node.localName || node.nodeName.split(":").pop();
var reader = group[local] || group["*"];
if(reader) {
reader.apply(this, [node, obj]);
}
}
return obj;
},
/**
* Method: readChildNodes
* Shorthand for applying the named readers to all children of a node.
* For each child of type 1 (element), <readSelf> is called.
*
* Parameters:
* node - {DOMElement} The node to be read (required).
* obj - {Object} The object to be modified (optional).
*
* Returns:
* {Object} The input object, modified.
*/
readChildNodes: function(node, obj) {
if(!obj) {
obj = {};
}
var children = node.childNodes;
var child;
for(var i=0, len=children.length; i<len; ++i) {
child = children[i];
if(child.nodeType == 1) {
this.readNode(child, obj);
}
}
return obj;
},
/**
* Method: writeNode
* Shorthand for applying one of the named writers and appending the
* results to a node. If a qualified name is not provided for the
* second argument (and a local name is used instead), the namespace
* of the parent node will be assumed.
*
* Parameters:
* name - {String} The name of a node to generate. If a qualified name
* (e.g. "pre:Name") is used, the namespace prefix is assumed to be
* in the <writers> group. If a local name is used (e.g. "Name") then
* the namespace of the parent is assumed. If a local name is used
* and no parent is supplied, then the default namespace is assumed.
* obj - {Object} Structure containing data for the writer.
* parent - {DOMElement} Result will be appended to this node. If no parent
* is supplied, the node will not be appended to anything.
*
* Returns:
* {DOMElement} The child node.
*/
writeNode: function(name, obj, parent) {
var prefix, local;
var split = name.indexOf(":");
if(split > 0) {
prefix = name.substring(0, split);
local = name.substring(split + 1);
} else {
if(parent) {
prefix = this.namespaceAlias[parent.namespaceURI];
} else {
prefix = this.defaultPrefix;
}
local = name;
}
var child = this.writers[prefix][local].apply(this, [obj]);
if(parent) {
parent.appendChild(child);
}
return child;
},
/**
* APIMethod: getChildEl
* Get the first child element. Optionally only return the first child
* if it matches the given name and namespace URI.
*
* Parameters:
* node - {DOMElement} The parent node.
* name - {String} Optional node name (local) to search for.
* uri - {String} Optional namespace URI to search for.
*
* Returns:
* {DOMElement} The first child. Returns null if no element is found, if
* something significant besides an element is found, or if the element
* found does not match the optional name and uri.
*/
getChildEl: function(node, name, uri) {
return node && this.getThisOrNextEl(node.firstChild, name, uri);
},
/**
* APIMethod: getNextEl
* Get the next sibling element. Optionally get the first sibling only
* if it matches the given local name and namespace URI.
*
* Parameters:
* node - {DOMElement} The node.
* name - {String} Optional local name of the sibling to search for.
* uri - {String} Optional namespace URI of the sibling to search for.
*
* Returns:
* {DOMElement} The next sibling element. Returns null if no element is
* found, something significant besides an element is found, or the
* found element does not match the optional name and uri.
*/
getNextEl: function(node, name, uri) {
return node && this.getThisOrNextEl(node.nextSibling, name, uri);
},
/**
* Method: getThisOrNextEl
* Return this node or the next element node. Optionally get the first
* sibling with the given local name or namespace URI.
*
* Parameters:
* node - {DOMElement} The node.
* name - {String} Optional local name of the sibling to search for.
* uri - {String} Optional namespace URI of the sibling to search for.
*
* Returns:
* {DOMElement} The next sibling element. Returns null if no element is
* found, something significant besides an element is found, or the
* found element does not match the query.
*/
getThisOrNextEl: function(node, name, uri) {
outer: for(var sibling=node; sibling; sibling=sibling.nextSibling) {
switch(sibling.nodeType) {
case 1: // Element
if((!name || name === (sibling.localName || sibling.nodeName.split(":").pop())) &&
(!uri || uri === sibling.namespaceURI)) {
// matches
break outer;
}
sibling = null;
break outer;
case 3: // Text
if(/^\s*$/.test(sibling.nodeValue)) {
break;
}
case 4: // CDATA
case 6: // ENTITY_NODE
case 12: // NOTATION_NODE
case 10: // DOCUMENT_TYPE_NODE
case 11: // DOCUMENT_FRAGMENT_NODE
sibling = null;
break outer;
} // ignore comments and processing instructions
}
return sibling || null;
},
/**
* APIMethod: lookupNamespaceURI
* Takes a prefix and returns the namespace URI associated with it on the given
* node if found (and null if not). Supplying null for the prefix will
* return the default namespace.
*
* For browsers that support it, this calls the native lookupNamesapceURI
* function. In other browsers, this is an implementation of
* http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespaceURI.
*
* For browsers that don't support the attribute.ownerElement property, this
* method cannot be called on attribute nodes.
*
* Parameters:
* node - {DOMElement} The node from which to start looking.
* prefix - {String} The prefix to lookup or null to lookup the default namespace.
*
* Returns:
* {String} The namespace URI for the given prefix. Returns null if the prefix
* cannot be found or the node is the wrong type.
*/
lookupNamespaceURI: function(node, prefix) {
var uri = null;
if(node) {
if(node.lookupNamespaceURI) {
uri = node.lookupNamespaceURI(prefix);
} else {
outer: switch(node.nodeType) {
case 1: // ELEMENT_NODE
if(node.namespaceURI !== null && node.prefix === prefix) {
uri = node.namespaceURI;
break outer;
}
var len = node.attributes.length;
if(len) {
var attr;
for(var i=0; i<len; ++i) {
attr = node.attributes[i];
if(attr.prefix === "xmlns" && attr.name === "xmlns:" + prefix) {
uri = attr.value || null;
break outer;
} else if(attr.name === "xmlns" && prefix === null) {
uri = attr.value || null;
break outer;
}
}
}
uri = this.lookupNamespaceURI(node.parentNode, prefix);
break outer;
case 2: // ATTRIBUTE_NODE
uri = this.lookupNamespaceURI(node.ownerElement, prefix);
break outer;
case 9: // DOCUMENT_NODE
uri = this.lookupNamespaceURI(node.documentElement, prefix);
break outer;
case 6: // ENTITY_NODE
case 12: // NOTATION_NODE
case 10: // DOCUMENT_TYPE_NODE
case 11: // DOCUMENT_FRAGMENT_NODE
break outer;
default:
// TEXT_NODE (3), CDATA_SECTION_NODE (4), ENTITY_REFERENCE_NODE (5),
// PROCESSING_INSTRUCTION_NODE (7), COMMENT_NODE (8)
uri = this.lookupNamespaceURI(node.parentNode, prefix);
break outer;
}
}
}
return uri;
},
/**
* Method: getXMLDoc
* Get an XML document for nodes that are not supported in HTML (e.g.
* createCDATASection). On IE, this will either return an existing or
* create a new <xmldom> on the instance. On other browsers, this will
* either return an existing or create a new shared document (see
* <OpenLayers.Format.XML.document>).
*
* Returns:
* {XMLDocument}
*/
getXMLDoc: function() {
if (!OpenLayers.Format.XML.document && !this.xmldom) {
if (document.implementation && document.implementation.createDocument) {
OpenLayers.Format.XML.document =
document.implementation.createDocument("", "", null);
} else if (!this.xmldom && OpenLayers.Format.XML.supportActiveX) {
this.xmldom = new ActiveXObject("Microsoft.XMLDOM");
}
}
return OpenLayers.Format.XML.document || this.xmldom;
},
CLASS_NAME: "OpenLayers.Format.XML"
});
OpenLayers.Format.XML.CONTENT_TYPE = {EMPTY: 0, SIMPLE: 1, COMPLEX: 2, MIXED: 3};
/**
* APIFunction: OpenLayers.Format.XML.lookupNamespaceURI
* Takes a prefix and returns the namespace URI associated with it on the given
* node if found (and null if not). Supplying null for the prefix will
* return the default namespace.
*
* For browsers that support it, this calls the native lookupNamesapceURI
* function. In other browsers, this is an implementation of
* http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespaceURI.
*
* For browsers that don't support the attribute.ownerElement property, this
* method cannot be called on attribute nodes.
*
* Parameters:
* node - {DOMElement} The node from which to start looking.
* prefix - {String} The prefix to lookup or null to lookup the default namespace.
*
* Returns:
* {String} The namespace URI for the given prefix. Returns null if the prefix
* cannot be found or the node is the wrong type.
*/
OpenLayers.Format.XML.lookupNamespaceURI = OpenLayers.Function.bind(
OpenLayers.Format.XML.prototype.lookupNamespaceURI,
OpenLayers.Format.XML.prototype
);
/**
* Property: OpenLayers.Format.XML.document
* {XMLDocument} XML document to reuse for creating non-HTML compliant nodes,
* like document.createCDATASection.
*/
OpenLayers.Format.XML.document = null;
/**
* APIFunction: OpenLayers.Format.XML.supportActiveX
* Returns a poolean flag to check if this browser uses ActiveX.
*/
OpenLayers.Format.XML.supportActiveX = (function () {
return (Object.getOwnPropertyDescriptor &&
Object.getOwnPropertyDescriptor(window, "ActiveXObject")) ||
("ActiveXObject" in window);
})();
/* ======================================================================
OpenLayers/Format/WFST.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Format.js
*/
/**
* Function: OpenLayers.Format.WFST
* Used to create a versioned WFS protocol. Default version is 1.0.0.
*
* Returns:
* {<OpenLayers.Format>} A WFST format of the given version.
*/
OpenLayers.Format.WFST = function(options) {
options = OpenLayers.Util.applyDefaults(
options, OpenLayers.Format.WFST.DEFAULTS
);
var cls = OpenLayers.Format.WFST["v"+options.version.replace(/\./g, "_")];
if(!cls) {
throw "Unsupported WFST version: " + options.version;
}
return new cls(options);
};
/**
* Constant: OpenLayers.Format.WFST.DEFAULTS
* {Object} Default properties for the WFST format.
*/
OpenLayers.Format.WFST.DEFAULTS = {
"version": "1.0.0"
};
/* ======================================================================
OpenLayers/Filter.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/BaseTypes/Class.js
* @requires OpenLayers/Util.js
* @requires OpenLayers/Style.js
*/
/**
* Class: OpenLayers.Filter
* This class represents an OGC Filter.
*/
OpenLayers.Filter = OpenLayers.Class({
/**
* Constructor: OpenLayers.Filter
* This class represents a generic filter.
*
* Parameters:
* options - {Object} Optional object whose properties will be set on the
* instance.
*
* Returns:
* {<OpenLayers.Filter>}
*/
initialize: function(options) {
OpenLayers.Util.extend(this, options);
},
/**
* APIMethod: destroy
* Remove reference to anything added.
*/
destroy: function() {
},
/**
* APIMethod: evaluate
* Evaluates this filter in a specific context. Instances or subclasses
* are supposed to override this method.
*
* Parameters:
* context - {Object} Context to use in evaluating the filter. If a vector
* feature is provided, the feature.attributes will be used as context.
*
* Returns:
* {Boolean} The filter applies.
*/
evaluate: function(context) {
return true;
},
/**
* APIMethod: clone
* Clones this filter. Should be implemented by subclasses.
*
* Returns:
* {<OpenLayers.Filter>} Clone of this filter.
*/
clone: function() {
return null;
},
/**
* APIMethod: toString
*
* Returns:
* {String} Include <OpenLayers.Format.CQL> in your build to get a CQL
* representation of the filter returned. Otherwise "[Object object]"
* will be returned.
*/
toString: function() {
var string;
if (OpenLayers.Format && OpenLayers.Format.CQL) {
string = OpenLayers.Format.CQL.prototype.write(this);
} else {
string = Object.prototype.toString.call(this);
}
return string;
},
CLASS_NAME: "OpenLayers.Filter"
});
/* ======================================================================
OpenLayers/Filter/Spatial.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Filter.js
*/
/**
* Class: OpenLayers.Filter.Spatial
* This class represents a spatial filter.
* Currently implemented: BBOX, DWithin and Intersects
*
* Inherits from:
* - <OpenLayers.Filter>
*/
OpenLayers.Filter.Spatial = OpenLayers.Class(OpenLayers.Filter, {
/**
* APIProperty: type
* {String} Type of spatial filter.
*
* The type should be one of:
* - OpenLayers.Filter.Spatial.BBOX
* - OpenLayers.Filter.Spatial.INTERSECTS
* - OpenLayers.Filter.Spatial.DWITHIN
* - OpenLayers.Filter.Spatial.WITHIN
* - OpenLayers.Filter.Spatial.CONTAINS
*/
type: null,
/**
* APIProperty: property
* {String} Name of the context property to compare.
*/
property: null,
/**
* APIProperty: value
* {<OpenLayers.Bounds> || <OpenLayers.Geometry>} The bounds or geometry
* to be used by the filter. Use bounds for BBOX filters and geometry
* for INTERSECTS or DWITHIN filters.
*/
value: null,
/**
* APIProperty: distance
* {Number} The distance to use in a DWithin spatial filter.
*/
distance: null,
/**
* APIProperty: distanceUnits
* {String} The units to use for the distance, e.g. 'm'.
*/
distanceUnits: null,
/**
* Constructor: OpenLayers.Filter.Spatial
* Creates a spatial filter.
*
* Parameters:
* options - {Object} An optional object with properties to set on the
* filter.
*
* Returns:
* {<OpenLayers.Filter.Spatial>}
*/
/**
* Method: evaluate
* Evaluates this filter for a specific feature.
*
* Parameters:
* feature - {<OpenLayers.Feature.Vector>} feature to apply the filter to.
*
* Returns:
* {Boolean} The feature meets filter criteria.
*/
evaluate: function(feature) {
var intersect = false;
switch(this.type) {
case OpenLayers.Filter.Spatial.BBOX:
case OpenLayers.Filter.Spatial.INTERSECTS:
if(feature.geometry) {
var geom = this.value;
if(this.value.CLASS_NAME == "OpenLayers.Bounds") {
geom = this.value.toGeometry();
}
if(feature.geometry.intersects(geom)) {
intersect = true;
}
}
break;
default:
throw new Error('evaluate is not implemented for this filter type.');
}
return intersect;
},
/**
* APIMethod: clone
* Clones this filter.
*
* Returns:
* {<OpenLayers.Filter.Spatial>} Clone of this filter.
*/
clone: function() {
var options = OpenLayers.Util.applyDefaults({
value: this.value && this.value.clone && this.value.clone()
}, this);
return new OpenLayers.Filter.Spatial(options);
},
CLASS_NAME: "OpenLayers.Filter.Spatial"
});
OpenLayers.Filter.Spatial.BBOX = "BBOX";
OpenLayers.Filter.Spatial.INTERSECTS = "INTERSECTS";
OpenLayers.Filter.Spatial.DWITHIN = "DWITHIN";
OpenLayers.Filter.Spatial.WITHIN = "WITHIN";
OpenLayers.Filter.Spatial.CONTAINS = "CONTAINS";
/* ======================================================================
OpenLayers/Filter/FeatureId.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Filter.js
*/
/**
* Class: OpenLayers.Filter.FeatureId
* This class represents a ogc:FeatureId Filter, as being used for rule-based SLD
* styling
*
* Inherits from:
* - <OpenLayers.Filter>
*/
OpenLayers.Filter.FeatureId = OpenLayers.Class(OpenLayers.Filter, {
/**
* APIProperty: fids
* {Array(String)} Feature Ids to evaluate this rule against.
* To be passed inside the params object.
*/
fids: null,
/**
* Property: type
* {String} Type to identify this filter.
*/
type: "FID",
/**
* Constructor: OpenLayers.Filter.FeatureId
* Creates an ogc:FeatureId rule.
*
* Parameters:
* options - {Object} An optional object with properties to set on the
* rule
*
* Returns:
* {<OpenLayers.Filter.FeatureId>}
*/
initialize: function(options) {
this.fids = [];
OpenLayers.Filter.prototype.initialize.apply(this, [options]);
},
/**
* APIMethod: evaluate
* evaluates this rule for a specific feature
*
* Parameters:
* feature - {<OpenLayers.Feature>} feature to apply the rule to.
* For vector features, the check is run against the fid,
* for plain features against the id.
*
* Returns:
* {Boolean} true if the rule applies, false if it does not
*/
evaluate: function(feature) {
for (var i=0, len=this.fids.length; i<len; i++) {
var fid = feature.fid || feature.id;
if (fid == this.fids[i]) {
return true;
}
}
return false;
},
/**
* APIMethod: clone
* Clones this filter.
*
* Returns:
* {<OpenLayers.Filter.FeatureId>} Clone of this filter.
*/
clone: function() {
var filter = new OpenLayers.Filter.FeatureId();
OpenLayers.Util.extend(filter, this);
filter.fids = this.fids.slice();
return filter;
},
CLASS_NAME: "OpenLayers.Filter.FeatureId"
});
/* ======================================================================
OpenLayers/Format/WFST/v1.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Format/XML.js
* @requires OpenLayers/Format/WFST.js
* @requires OpenLayers/Filter/Spatial.js
* @requires OpenLayers/Filter/FeatureId.js
*/
/**
* Class: OpenLayers.Format.WFST.v1
* Superclass for WFST parsers.
*
* Inherits from:
* - <OpenLayers.Format.XML>
*/
OpenLayers.Format.WFST.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
/**
* Property: namespaces
* {Object} Mapping of namespace aliases to namespace URIs.
*/
namespaces: {
xlink: "http://www.w3.org/1999/xlink",
xsi: "http://www.w3.org/2001/XMLSchema-instance",
wfs: "http://www.opengis.net/wfs",
gml: "http://www.opengis.net/gml",
ogc: "http://www.opengis.net/ogc",
ows: "http://www.opengis.net/ows",
xmlns: "http://www.w3.org/2000/xmlns/"
},
/**
* Property: defaultPrefix
*/
defaultPrefix: "wfs",
/**
* Property: version
* {String} WFS version number.
*/
version: null,
/**
* Property: schemaLocation
* {String} Schema location for a particular minor version.
*/
schemaLocations: null,
/**
* APIProperty: srsName
* {String} URI for spatial reference system.
*/
srsName: null,
/**
* APIProperty: extractAttributes
* {Boolean} Extract attributes from GML. Default is true.
*/
extractAttributes: true,
/**
* APIProperty: xy
* {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
* Changing is not recommended, a new Format should be instantiated.
*/
xy: true,
/**
* Property: stateName
* {Object} Maps feature states to node names.
*/
stateName: null,
/**
* Constructor: OpenLayers.Format.WFST.v1
* Instances of this class are not created directly. Use the
* <OpenLayers.Format.WFST.v1_0_0> or <OpenLayers.Format.WFST.v1_1_0>
* constructor instead.
*
* Parameters:
* options - {Object} An optional object whose properties will be set on
* this instance.
*/
initialize: function(options) {
// set state name mapping
this.stateName = {};
this.stateName[OpenLayers.State.INSERT] = "wfs:Insert";
this.stateName[OpenLayers.State.UPDATE] = "wfs:Update";
this.stateName[OpenLayers.State.DELETE] = "wfs:Delete";
OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
},
/**
* Method: getSrsName
*/
getSrsName: function(feature, options) {
var srsName = options && options.srsName;
if(!srsName) {
if(feature && feature.layer) {
srsName = feature.layer.projection.getCode();
} else {
srsName = this.srsName;
}
}
return srsName;
},
/**
* APIMethod: read
* Parse the response from a transaction. Because WFS is split into
* Transaction requests (create, update, and delete) and GetFeature
* requests (read), this method handles parsing of both types of
* responses.
*
* Parameters:
* data - {String | Document} The WFST document to read
* options - {Object} Options for the reader
*
* Valid options properties:
* output - {String} either "features" or "object". The default is
* "features", which means that the method will return an array of
* features. If set to "object", an object with a "features" property
* and other properties read by the parser will be returned.
*
* Returns:
* {Array | Object} Output depending on the output option.
*/
read: function(data, options) {
options = options || {};
OpenLayers.Util.applyDefaults(options, {
output: "features"
});
if(typeof data == "string") {
data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
}
if(data && data.nodeType == 9) {
data = data.documentElement;
}
var obj = {};
if(data) {
this.readNode(data, obj, true);
}
if(obj.features && options.output === "features") {
obj = obj.features;
}
return obj;
},
/**
* Property: readers
* Contains public functions, grouped by namespace prefix, that will
* be applied when a namespaced node is found matching the function
* name. The function will be applied in the scope of this parser
* with two arguments: the node being read and a context object passed
* from the parent.
*/
readers: {
"wfs": {
"FeatureCollection": function(node, obj) {
obj.features = [];
this.readChildNodes(node, obj);
}
}
},
/**
* Method: write
* Given an array of features, write a WFS transaction. This assumes
* the features have a state property that determines the operation
* type - insert, update, or delete.
*
* Parameters:
* features - {Array(<OpenLayers.Feature.Vector>)} A list of features. See
* below for a more detailed description of the influence of the
* feature's *modified* property.
* options - {Object}
*
* feature.modified rules:
* If a feature has a modified property set, the following checks will be
* made before a feature's geometry or attribute is included in an Update
* transaction:
* - *modified* is not set at all: The geometry and all attributes will be
* included.
* - *modified.geometry* is set (null or a geometry): The geometry will be
* included. If *modified.attributes* is not set, all attributes will
* be included.
* - *modified.attributes* is set: Only the attributes set in
* *modified.attributes* will be included.
* If *modified.geometry* is not set, the geometry will not be included.
*
* Valid options include:
* - *multi* {Boolean} If set to true, geometries will be casted to
* Multi geometries before writing.
*
* Returns:
* {String} A serialized WFS transaction.
*/
write: function(features, options) {
var node = this.writeNode("wfs:Transaction", {
features:features,
options: options
});
var value = this.schemaLocationAttr();
if(value) {
this.setAttributeNS(
node, this.namespaces["xsi"], "xsi:schemaLocation", value
);
}
return OpenLayers.Format.XML.prototype.write.apply(this, [node]);
},
/**
* Property: writers
* As a compliment to the readers property, this structure contains public
* writing functions grouped by namespace alias and named like the
* node names they produce.
*/
writers: {
"wfs": {
"GetFeature": function(options) {
var node = this.createElementNSPlus("wfs:GetFeature", {
attributes: {
service: "WFS",
version: this.version,
handle: options && options.handle,
outputFormat: options && options.outputFormat,
maxFeatures: options && options.maxFeatures,
viewParams: options && options.viewParams,
"xsi:schemaLocation": this.schemaLocationAttr(options)
}
});
if (typeof this.featureType == "string") {
this.writeNode("Query", options, node);
} else {
for (var i=0,len = this.featureType.length; i<len; i++) {
options.featureType = this.featureType[i];
this.writeNode("Query", options, node);
}
}
return node;
},
"Transaction": function(obj) {
obj = obj || {};
var options = obj.options || {};
var node = this.createElementNSPlus("wfs:Transaction", {
attributes: {
service: "WFS",
version: this.version,
handle: options.handle
}
});
var i, len;
var features = obj.features;
if(features) {
// temporarily re-assigning geometry types
if (options.multi === true) {
OpenLayers.Util.extend(this.geometryTypes, {
"OpenLayers.Geometry.Point": "MultiPoint",
"OpenLayers.Geometry.LineString": (this.multiCurve === true) ? "MultiCurve": "MultiLineString",
"OpenLayers.Geometry.Polygon": (this.multiSurface === true) ? "MultiSurface" : "MultiPolygon"
});
}
var name, feature;
for(i=0, len=features.length; i<len; ++i) {
feature = features[i];
name = this.stateName[feature.state];
if(name) {
this.writeNode(name, {
feature: feature,
options: options
}, node);
}
}
// switch back to original geometry types assignment
if (options.multi === true) {
this.setGeometryTypes();
}
}
if (options.nativeElements) {
for (i=0, len=options.nativeElements.length; i<len; ++i) {
this.writeNode("wfs:Native",
options.nativeElements[i], node);
}
}
return node;
},
"Native": function(nativeElement) {
var node = this.createElementNSPlus("wfs:Native", {
attributes: {
vendorId: nativeElement.vendorId,
safeToIgnore: nativeElement.safeToIgnore
},
value: nativeElement.value
});
return node;
},
"Insert": function(obj) {
var feature = obj.feature;
var options = obj.options;
var node = this.createElementNSPlus("wfs:Insert", {
attributes: {
handle: options && options.handle
}
});
this.srsName = this.getSrsName(feature);
this.writeNode("feature:_typeName", feature, node);
return node;
},
"Update": function(obj) {
var feature = obj.feature;
var options = obj.options;
var node = this.createElementNSPlus("wfs:Update", {
attributes: {
handle: options && options.handle,
typeName: (this.featureNS ? this.featurePrefix + ":" : "") +
this.featureType
}
});
if(this.featureNS) {
this.setAttributeNS(
node, this.namespaces.xmlns,
"xmlns:" + this.featurePrefix, this.featureNS
);
}
// add in geometry
var modified = feature.modified;
if (this.geometryName !== null && (!modified || modified.geometry !== undefined)) {
this.srsName = this.getSrsName(feature);
this.writeNode(
"Property", {name: this.geometryName, value: feature.geometry}, node
);
}
// add in attributes
for(var key in feature.attributes) {
if(feature.attributes[key] !== undefined &&
(!modified || !modified.attributes ||
(modified.attributes && (key in modified.attributes)))) {
this.writeNode(
"Property", {name: key, value: feature.attributes[key]}, node
);
}
}
// add feature id filter
this.writeNode("ogc:Filter", new OpenLayers.Filter.FeatureId({
fids: [feature.fid]
}), node);
return node;
},
"Property": function(obj) {
var node = this.createElementNSPlus("wfs:Property");
this.writeNode("Name", obj.name, node);
if(obj.value !== null) {
this.writeNode("Value", obj.value, node);
}
return node;
},
"Name": function(name) {
return this.createElementNSPlus("wfs:Name", {value: name});
},
"Value": function(obj) {
var node;
if(obj instanceof OpenLayers.Geometry) {
node = this.createElementNSPlus("wfs:Value");
var geom = this.writeNode("feature:_geometry", obj).firstChild;
node.appendChild(geom);
} else {
node = this.createElementNSPlus("wfs:Value", {value: obj});
}
return node;
},
"Delete": function(obj) {
var feature = obj.feature;
var options = obj.options;
var node = this.createElementNSPlus("wfs:Delete", {
attributes: {
handle: options && options.handle,
typeName: (this.featureNS ? this.featurePrefix + ":" : "") +
this.featureType
}
});
if(this.featureNS) {
this.setAttributeNS(
node, this.namespaces.xmlns,
"xmlns:" + this.featurePrefix, this.featureNS
);
}
this.writeNode("ogc:Filter", new OpenLayers.Filter.FeatureId({
fids: [feature.fid]
}), node);
return node;
}
}
},
/**
* Method: schemaLocationAttr
* Generate the xsi:schemaLocation attribute value.
*
* Returns:
* {String} The xsi:schemaLocation attribute or undefined if none.
*/
schemaLocationAttr: function(options) {
options = OpenLayers.Util.extend({
featurePrefix: this.featurePrefix,
schema: this.schema
}, options);
var schemaLocations = OpenLayers.Util.extend({}, this.schemaLocations);
if(options.schema) {
schemaLocations[options.featurePrefix] = options.schema;
}
var parts = [];
var uri;
for(var key in schemaLocations) {
uri = this.namespaces[key];
if(uri) {
parts.push(uri + " " + schemaLocations[key]);
}
}
var value = parts.join(" ") || undefined;
return value;
},
/**
* Method: setFilterProperty
* Set the property of each spatial filter.
*
* Parameters:
* filter - {<OpenLayers.Filter>}
*/
setFilterProperty: function(filter) {
if(filter.filters) {
for(var i=0, len=filter.filters.length; i<len; ++i) {
OpenLayers.Format.WFST.v1.prototype.setFilterProperty.call(this, filter.filters[i]);
}
} else {
if(filter instanceof OpenLayers.Filter.Spatial && !filter.property) {
// got a spatial filter without property, so set it
filter.property = this.geometryName;
}
}
},
CLASS_NAME: "OpenLayers.Format.WFST.v1"
});
/* ======================================================================
OpenLayers/Geometry.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/BaseTypes/Class.js
*/
/**
* Class: OpenLayers.Geometry
* A Geometry is a description of a geographic object. Create an instance of
* this class with the <OpenLayers.Geometry> constructor. This is a base class,
* typical geometry types are described by subclasses of this class.
*
* Note that if you use the <OpenLayers.Geometry.fromWKT> method, you must
* explicitly include the OpenLayers.Format.WKT in your build.
*/
OpenLayers.Geometry = OpenLayers.Class({
/**
* Property: id
* {String} A unique identifier for this geometry.
*/
id: null,
/**
* Property: parent
* {<OpenLayers.Geometry>}This is set when a Geometry is added as component
* of another geometry
*/
parent: null,
/**
* Property: bounds
* {<OpenLayers.Bounds>} The bounds of this geometry
*/
bounds: null,
/**
* Constructor: OpenLayers.Geometry
* Creates a geometry object.
*/
initialize: function() {
this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME+ "_");
},
/**
* Method: destroy
* Destroy this geometry.
*/
destroy: function() {
this.id = null;
this.bounds = null;
},
/**
* APIMethod: clone
* Create a clone of this geometry. Does not set any non-standard
* properties of the cloned geometry.
*
* Returns:
* {<OpenLayers.Geometry>} An exact clone of this geometry.
*/
clone: function() {
return new OpenLayers.Geometry();
},
/**
* Method: setBounds
* Set the bounds for this Geometry.
*
* Parameters:
* bounds - {<OpenLayers.Bounds>}
*/
setBounds: function(bounds) {
if (bounds) {
this.bounds = bounds.clone();
}
},
/**
* Method: clearBounds
* Nullify this components bounds and that of its parent as well.
*/
clearBounds: function() {
this.bounds = null;
if (this.parent) {
this.parent.clearBounds();
}
},
/**
* Method: extendBounds
* Extend the existing bounds to include the new bounds.
* If geometry's bounds is not yet set, then set a new Bounds.
*
* Parameters:
* newBounds - {<OpenLayers.Bounds>}
*/
extendBounds: function(newBounds){
var bounds = this.getBounds();
if (!bounds) {
this.setBounds(newBounds);
} else {
this.bounds.extend(newBounds);
}
},
/**
* APIMethod: getBounds
* Get the bounds for this Geometry. If bounds is not set, it
* is calculated again, this makes queries faster.
*
* Returns:
* {<OpenLayers.Bounds>}
*/
getBounds: function() {
if (this.bounds == null) {
this.calculateBounds();
}
return this.bounds;
},
/**
* APIMethod: calculateBounds
* Recalculate the bounds for the geometry.
*/
calculateBounds: function() {
//
// This should be overridden by subclasses.
//
},
/**
* APIMethod: distanceTo
* Calculate the closest distance between two geometries (on the x-y plane).
*
* Parameters:
* geometry - {<OpenLayers.Geometry>} The target geometry.
* options - {Object} Optional properties for configuring the distance
* calculation.
*
* Valid options depend on the specific geometry type.
*
* Returns:
* {Number | Object} The distance between this geometry and the target.
* If details is true, the return will be an object with distance,
* x0, y0, x1, and x2 properties. The x0 and y0 properties represent
* the coordinates of the closest point on this geometry. The x1 and y1
* properties represent the coordinates of the closest point on the
* target geometry.
*/
distanceTo: function(geometry, options) {
},
/**
* APIMethod: getVertices
* Return a list of all points in this geometry.
*
* Parameters:
* nodes - {Boolean} For lines, only return vertices that are
* endpoints. If false, for lines, only vertices that are not
* endpoints will be returned. If not provided, all vertices will
* be returned.
*
* Returns:
* {Array} A list of all vertices in the geometry.
*/
getVertices: function(nodes) {
},
/**
* Method: atPoint
* Note - This is only an approximation based on the bounds of the
* geometry.
*
* Parameters:
* lonlat - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
* object with a 'lon' and 'lat' properties.
* toleranceLon - {float} Optional tolerance in Geometric Coords
* toleranceLat - {float} Optional tolerance in Geographic Coords
*
* Returns:
* {Boolean} Whether or not the geometry is at the specified location
*/
atPoint: function(lonlat, toleranceLon, toleranceLat) {
var atPoint = false;
var bounds = this.getBounds();
if ((bounds != null) && (lonlat != null)) {
var dX = (toleranceLon != null) ? toleranceLon : 0;
var dY = (toleranceLat != null) ? toleranceLat : 0;
var toleranceBounds =
new OpenLayers.Bounds(this.bounds.left - dX,
this.bounds.bottom - dY,
this.bounds.right + dX,
this.bounds.top + dY);
atPoint = toleranceBounds.containsLonLat(lonlat);
}
return atPoint;
},
/**
* Method: getLength
* Calculate the length of this geometry. This method is defined in
* subclasses.
*
* Returns:
* {Float} The length of the collection by summing its parts
*/
getLength: function() {
//to be overridden by geometries that actually have a length
//
return 0.0;
},
/**
* Method: getArea
* Calculate the area of this geometry. This method is defined in subclasses.
*
* Returns:
* {Float} The area of the collection by summing its parts
*/
getArea: function() {
//to be overridden by geometries that actually have an area
//
return 0.0;
},
/**
* APIMethod: getCentroid
* Calculate the centroid of this geometry. This method is defined in subclasses.
*
* Returns:
* {<OpenLayers.Geometry.Point>} The centroid of the collection
*/
getCentroid: function() {
return null;
},
/**
* Method: toString
* Returns a text representation of the geometry. If the WKT format is
* included in a build, this will be the Well-Known Text
* representation.
*
* Returns:
* {String} String representation of this geometry.
*/
toString: function() {
var string;
if (OpenLayers.Format && OpenLayers.Format.WKT) {
string = OpenLayers.Format.WKT.prototype.write(
new OpenLayers.Feature.Vector(this)
);
} else {
string = Object.prototype.toString.call(this);
}
return string;
},
CLASS_NAME: "OpenLayers.Geometry"
});
/**
* Function: OpenLayers.Geometry.fromWKT
* Generate a geometry given a Well-Known Text string. For this method to
* work, you must include the OpenLayers.Format.WKT in your build
* explicitly.
*
* Parameters:
* wkt - {String} A string representing the geometry in Well-Known Text.
*
* Returns:
* {<OpenLayers.Geometry>} A geometry of the appropriate class.
*/
OpenLayers.Geometry.fromWKT = function(wkt) {
var geom;
if (OpenLayers.Format && OpenLayers.Format.WKT) {
var format = OpenLayers.Geometry.fromWKT.format;
if (!format) {
format = new OpenLayers.Format.WKT();
OpenLayers.Geometry.fromWKT.format = format;
}
var result = format.read(wkt);
if (result instanceof OpenLayers.Feature.Vector) {
geom = result.geometry;
} else if (OpenLayers.Util.isArray(result)) {
var len = result.length;
var components = new Array(len);
for (var i=0; i<len; ++i) {
components[i] = result[i].geometry;
}
geom = new OpenLayers.Geometry.Collection(components);
}
}
return geom;
};
/**
* Method: OpenLayers.Geometry.segmentsIntersect
* Determine whether two line segments intersect. Optionally calculates
* and returns the intersection point. This function is optimized for
* cases where seg1.x2 >= seg2.x1 || seg2.x2 >= seg1.x1. In those
* obvious cases where there is no intersection, the function should
* not be called.
*
* Parameters:
* seg1 - {Object} Object representing a segment with properties x1, y1, x2,
* and y2. The start point is represented by x1 and y1. The end point
* is represented by x2 and y2. Start and end are ordered so that x1 < x2.
* seg2 - {Object} Object representing a segment with properties x1, y1, x2,
* and y2. The start point is represented by x1 and y1. The end point
* is represented by x2 and y2. Start and end are ordered so that x1 < x2.
* options - {Object} Optional properties for calculating the intersection.
*
* Valid options:
* point - {Boolean} Return the intersection point. If false, the actual
* intersection point will not be calculated. If true and the segments
* intersect, the intersection point will be returned. If true and
* the segments do not intersect, false will be returned. If true and
* the segments are coincident, true will be returned.
* tolerance - {Number} If a non-null value is provided, if the segments are
* within the tolerance distance, this will be considered an intersection.
* In addition, if the point option is true and the calculated intersection
* is within the tolerance distance of an end point, the endpoint will be
* returned instead of the calculated intersection. Further, if the
* intersection is within the tolerance of endpoints on both segments, or
* if two segment endpoints are within the tolerance distance of eachother
* (but no intersection is otherwise calculated), an endpoint on the
* first segment provided will be returned.
*
* Returns:
* {Boolean | <OpenLayers.Geometry.Point>} The two segments intersect.
* If the point argument is true, the return will be the intersection
* point or false if none exists. If point is true and the segments
* are coincident, return will be true (and the instersection is equal
* to the shorter segment).
*/
OpenLayers.Geometry.segmentsIntersect = function(seg1, seg2, options) {
var point = options && options.point;
var tolerance = options && options.tolerance;
var intersection = false;
var x11_21 = seg1.x1 - seg2.x1;
var y11_21 = seg1.y1 - seg2.y1;
var x12_11 = seg1.x2 - seg1.x1;
var y12_11 = seg1.y2 - seg1.y1;
var y22_21 = seg2.y2 - seg2.y1;
var x22_21 = seg2.x2 - seg2.x1;
var d = (y22_21 * x12_11) - (x22_21 * y12_11);
var n1 = (x22_21 * y11_21) - (y22_21 * x11_21);
var n2 = (x12_11 * y11_21) - (y12_11 * x11_21);
if(d == 0) {
// parallel
if(n1 == 0 && n2 == 0) {
// coincident
intersection = true;
}
} else {
var along1 = n1 / d;
var along2 = n2 / d;
if(along1 >= 0 && along1 <= 1 && along2 >=0 && along2 <= 1) {
// intersect
if(!point) {
intersection = true;
} else {
// calculate the intersection point
var x = seg1.x1 + (along1 * x12_11);
var y = seg1.y1 + (along1 * y12_11);
intersection = new OpenLayers.Geometry.Point(x, y);
}
}
}
if(tolerance) {
var dist;
if(intersection) {
if(point) {
var segs = [seg1, seg2];
var seg, x, y;
// check segment endpoints for proximity to intersection
// set intersection to first endpoint within the tolerance
outer: for(var i=0; i<2; ++i) {
seg = segs[i];
for(var j=1; j<3; ++j) {
x = seg["x" + j];
y = seg["y" + j];
dist = Math.sqrt(
Math.pow(x - intersection.x, 2) +
Math.pow(y - intersection.y, 2)
);
if(dist < tolerance) {
intersection.x = x;
intersection.y = y;
break outer;
}
}
}
}
} else {
// no calculated intersection, but segments could be within
// the tolerance of one another
var segs = [seg1, seg2];
var source, target, x, y, p, result;
// check segment endpoints for proximity to intersection
// set intersection to first endpoint within the tolerance
outer: for(var i=0; i<2; ++i) {
source = segs[i];
target = segs[(i+1)%2];
for(var j=1; j<3; ++j) {
p = {x: source["x"+j], y: source["y"+j]};
result = OpenLayers.Geometry.distanceToSegment(p, target);
if(result.distance < tolerance) {
if(point) {
intersection = new OpenLayers.Geometry.Point(p.x, p.y);
} else {
intersection = true;
}
break outer;
}
}
}
}
}
return intersection;
};
/**
* Function: OpenLayers.Geometry.distanceToSegment
*
* Parameters:
* point - {Object} An object with x and y properties representing the
* point coordinates.
* segment - {Object} An object with x1, y1, x2, and y2 properties
* representing endpoint coordinates.
*
* Returns:
* {Object} An object with distance, along, x, and y properties. The distance
* will be the shortest distance between the input point and segment.
* The x and y properties represent the coordinates along the segment
* where the shortest distance meets the segment. The along attribute
* describes how far between the two segment points the given point is.
*/
OpenLayers.Geometry.distanceToSegment = function(point, segment) {
var result = OpenLayers.Geometry.distanceSquaredToSegment(point, segment);
result.distance = Math.sqrt(result.distance);
return result;
};
/**
* Function: OpenLayers.Geometry.distanceSquaredToSegment
*
* Usually the distanceToSegment function should be used. This variant however
* can be used for comparisons where the exact distance is not important.
*
* Parameters:
* point - {Object} An object with x and y properties representing the
* point coordinates.
* segment - {Object} An object with x1, y1, x2, and y2 properties
* representing endpoint coordinates.
*
* Returns:
* {Object} An object with squared distance, along, x, and y properties.
* The distance will be the shortest distance between the input point and
* segment. The x and y properties represent the coordinates along the
* segment where the shortest distance meets the segment. The along
* attribute describes how far between the two segment points the given
* point is.
*/
OpenLayers.Geometry.distanceSquaredToSegment = function(point, segment) {
var x0 = point.x;
var y0 = point.y;
var x1 = segment.x1;
var y1 = segment.y1;
var x2 = segment.x2;
var y2 = segment.y2;
var dx = x2 - x1;
var dy = y2 - y1;
var along = (dx == 0 && dy == 0) ? 0 : ((dx * (x0 - x1)) + (dy * (y0 - y1))) /
(Math.pow(dx, 2) + Math.pow(dy, 2));
var x, y;
if(along <= 0.0) {
x = x1;
y = y1;
} else if(along >= 1.0) {
x = x2;
y = y2;
} else {
x = x1 + along * dx;
y = y1 + along * dy;
}
return {
distance: Math.pow(x - x0, 2) + Math.pow(y - y0, 2),
x: x, y: y,
along: along
};
};
/* ======================================================================
OpenLayers/Geometry/Point.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Geometry.js
*/
/**
* Class: OpenLayers.Geometry.Point
* Point geometry class.
*
* Inherits from:
* - <OpenLayers.Geometry>
*/
OpenLayers.Geometry.Point = OpenLayers.Class(OpenLayers.Geometry, {
/**
* APIProperty: x
* {float}
*/
x: null,
/**
* APIProperty: y
* {float}
*/
y: null,
/**
* Constructor: OpenLayers.Geometry.Point
* Construct a point geometry.
*
* Parameters:
* x - {float}
* y - {float}
*
*/
initialize: function(x, y) {
OpenLayers.Geometry.prototype.initialize.apply(this, arguments);
this.x = parseFloat(x);
this.y = parseFloat(y);
},
/**
* APIMethod: clone
*
* Returns:
* {<OpenLayers.Geometry.Point>} An exact clone of this OpenLayers.Geometry.Point
*/
clone: function(obj) {
if (obj == null) {
obj = new OpenLayers.Geometry.Point(this.x, this.y);
}
// catch any randomly tagged-on properties
OpenLayers.Util.applyDefaults(obj, this);
return obj;
},
/**
* Method: calculateBounds
* Create a new Bounds based on the lon/lat
*/
calculateBounds: function () {
this.bounds = new OpenLayers.Bounds(this.x, this.y,
this.x, this.y);
},
/**
* APIMethod: distanceTo
* Calculate the closest distance between two geometries (on the x-y plane).
*
* Parameters:
* geometry - {<OpenLayers.Geometry>} The target geometry.
* options - {Object} Optional properties for configuring the distance
* calculation.
*
* Valid options:
* details - {Boolean} Return details from the distance calculation.
* Default is false.
* edge - {Boolean} Calculate the distance from this geometry to the
* nearest edge of the target geometry. Default is true. If true,
* calling distanceTo from a geometry that is wholly contained within
* the target will result in a non-zero distance. If false, whenever
* geometries intersect, calling distanceTo will return 0. If false,
* details cannot be returned.
*
* Returns:
* {Number | Object} The distance between this geometry and the target.
* If details is true, the return will be an object with distance,
* x0, y0, x1, and x2 properties. The x0 and y0 properties represent
* the coordinates of the closest point on this geometry. The x1 and y1
* properties represent the coordinates of the closest point on the
* target geometry.
*/
distanceTo: function(geometry, options) {
var edge = !(options && options.edge === false);
var details = edge && options && options.details;
var distance, x0, y0, x1, y1, result;
if(geometry instanceof OpenLayers.Geometry.Point) {
x0 = this.x;
y0 = this.y;
x1 = geometry.x;
y1 = geometry.y;
distance = Math.sqrt(Math.pow(x0 - x1, 2) + Math.pow(y0 - y1, 2));
result = !details ?
distance : {x0: x0, y0: y0, x1: x1, y1: y1, distance: distance};
} else {
result = geometry.distanceTo(this, options);
if(details) {
// switch coord order since this geom is target
result = {
x0: result.x1, y0: result.y1,
x1: result.x0, y1: result.y0,
distance: result.distance
};
}
}
return result;
},
/**
* APIMethod: equals
* Determine whether another geometry is equivalent to this one. Geometries
* are considered equivalent if all components have the same coordinates.
*
* Parameters:
* geom - {<OpenLayers.Geometry.Point>} The geometry to test.
*
* Returns:
* {Boolean} The supplied geometry is equivalent to this geometry.
*/
equals: function(geom) {
var equals = false;
if (geom != null) {
equals = ((this.x == geom.x && this.y == geom.y) ||
(isNaN(this.x) && isNaN(this.y) && isNaN(geom.x) && isNaN(geom.y)));
}
return equals;
},
/**
* Method: toShortString
*
* Returns:
* {String} Shortened String representation of Point object.
* (ex. <i>"5, 42"</i>)
*/
toShortString: function() {
return (this.x + ", " + this.y);
},
/**
* APIMethod: move
* Moves a geometry by the given displacement along positive x and y axes.
* This modifies the position of the geometry and clears the cached
* bounds.
*
* Parameters:
* x - {Float} Distance to move geometry in positive x direction.
* y - {Float} Distance to move geometry in positive y direction.
*/
move: function(x, y) {
this.x = this.x + x;
this.y = this.y + y;
this.clearBounds();
},
/**
* APIMethod: rotate
* Rotate a point around another.
*
* Parameters:
* angle - {Float} Rotation angle in degrees (measured counterclockwise
* from the positive x-axis)
* origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
*/
rotate: function(angle, origin) {
angle *= Math.PI / 180;
var radius = this.distanceTo(origin);
var theta = angle + Math.atan2(this.y - origin.y, this.x - origin.x);
this.x = origin.x + (radius * Math.cos(theta));
this.y = origin.y + (radius * Math.sin(theta));
this.clearBounds();
},
/**
* APIMethod: getCentroid
*
* Returns:
* {<OpenLayers.Geometry.Point>} The centroid of the collection
*/
getCentroid: function() {
return new OpenLayers.Geometry.Point(this.x, this.y);
},
/**
* APIMethod: resize
* Resize a point relative to some origin. For points, this has the effect
* of scaling a vector (from the origin to the point). This method is
* more useful on geometry collection subclasses.
*
* Parameters:
* scale - {Float} Ratio of the new distance from the origin to the old
* distance from the origin. A scale of 2 doubles the
* distance between the point and origin.
* origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
* ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.
*
* Returns:
* {<OpenLayers.Geometry>} - The current geometry.
*/
resize: function(scale, origin, ratio) {
ratio = (ratio == undefined) ? 1 : ratio;
this.x = origin.x + (scale * ratio * (this.x - origin.x));
this.y = origin.y + (scale * (this.y - origin.y));
this.clearBounds();
return this;
},
/**
* APIMethod: intersects
* Determine if the input geometry intersects this one.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>} Any type of geometry.
*
* Returns:
* {Boolean} The input geometry intersects this one.
*/
intersects: function(geometry) {
var intersect = false;
if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
intersect = this.equals(geometry);
} else {
intersect = geometry.intersects(this);
}
return intersect;
},
/**
* APIMethod: transform
* Translate the x,y properties of the point from source to dest.
*
* Parameters:
* source - {<OpenLayers.Projection>}
* dest - {<OpenLayers.Projection>}
*
* Returns:
* {<OpenLayers.Geometry>}
*/
transform: function(source, dest) {
if ((source && dest)) {
OpenLayers.Projection.transform(
this, source, dest);
this.bounds = null;
}
return this;
},
/**
* APIMethod: getVertices
* Return a list of all points in this geometry.
*
* Parameters:
* nodes - {Boolean} For lines, only return vertices that are
* endpoints. If false, for lines, only vertices that are not
* endpoints will be returned. If not provided, all vertices will
* be returned.
*
* Returns:
* {Array} A list of all vertices in the geometry.
*/
getVertices: function(nodes) {
return [this];
},
CLASS_NAME: "OpenLayers.Geometry.Point"
});
/* ======================================================================
OpenLayers/Geometry/Collection.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Geometry.js
*/
/**
* Class: OpenLayers.Geometry.Collection
* A Collection is exactly what it sounds like: A collection of different
* Geometries. These are stored in the local parameter <components> (which
* can be passed as a parameter to the constructor).
*
* As new geometries are added to the collection, they are NOT cloned.
* When removing geometries, they need to be specified by reference (ie you
* have to pass in the *exact* geometry to be removed).
*
* The <getArea> and <getLength> functions here merely iterate through
* the components, summing their respective areas and lengths.
*
* Create a new instance with the <OpenLayers.Geometry.Collection> constructor.
*
* Inherits from:
* - <OpenLayers.Geometry>
*/
OpenLayers.Geometry.Collection = OpenLayers.Class(OpenLayers.Geometry, {
/**
* APIProperty: components
* {Array(<OpenLayers.Geometry>)} The component parts of this geometry
*/
components: null,
/**
* Property: componentTypes
* {Array(String)} An array of class names representing the types of
* components that the collection can include. A null value means the
* component types are not restricted.
*/
componentTypes: null,
/**
* Constructor: OpenLayers.Geometry.Collection
* Creates a Geometry Collection -- a list of geoms.
*
* Parameters:
* components - {Array(<OpenLayers.Geometry>)} Optional array of geometries
*
*/
initialize: function (components) {
OpenLayers.Geometry.prototype.initialize.apply(this, arguments);
this.components = [];
if (components != null) {
this.addComponents(components);
}
},
/**
* APIMethod: destroy
* Destroy this geometry.
*/
destroy: function () {
this.components.length = 0;
this.components = null;
OpenLayers.Geometry.prototype.destroy.apply(this, arguments);
},
/**
* APIMethod: clone
* Clone this geometry.
*
* Returns:
* {<OpenLayers.Geometry.Collection>} An exact clone of this collection
*/
clone: function() {
var Constructor = OpenLayers.Util.getConstructor(this.CLASS_NAME);
var geometry = new Constructor();
for(var i=0, len=this.components.length; i<len; i++) {
geometry.addComponent(this.components[i].clone());
}
// catch any randomly tagged-on properties
OpenLayers.Util.applyDefaults(geometry, this);
return geometry;
},
/**
* Method: getComponentsString
* Get a string representing the components for this collection
*
* Returns:
* {String} A string representation of the components of this geometry
*/
getComponentsString: function(){
var strings = [];
for(var i=0, len=this.components.length; i<len; i++) {
strings.push(this.components[i].toShortString());
}
return strings.join(",");
},
/**
* APIMethod: calculateBounds
* Recalculate the bounds by iterating through the components and
* calling calling extendBounds() on each item.
*/
calculateBounds: function() {
this.bounds = null;
var bounds = new OpenLayers.Bounds();
var components = this.components;
if (components) {
for (var i=0, len=components.length; i<len; i++) {
bounds.extend(components[i].getBounds());
}
}
// to preserve old behavior, we only set bounds if non-null
// in the future, we could add bounds.isEmpty()
if (bounds.left != null && bounds.bottom != null &&
bounds.right != null && bounds.top != null) {
this.setBounds(bounds);
}
},
/**
* APIMethod: addComponents
* Add components to this geometry.
*
* Parameters:
* components - {Array(<OpenLayers.Geometry>)} An array of geometries to add
*/
addComponents: function(components){
if(!(OpenLayers.Util.isArray(components))) {
components = [components];
}
for(var i=0, len=components.length; i<len; i++) {
this.addComponent(components[i]);
}
},
/**
* Method: addComponent
* Add a new component (geometry) to the collection. If this.componentTypes
* is set, then the component class name must be in the componentTypes array.
*
* The bounds cache is reset.
*
* Parameters:
* component - {<OpenLayers.Geometry>} A geometry to add
* index - {int} Optional index into the array to insert the component
*
* Returns:
* {Boolean} The component geometry was successfully added
*/
addComponent: function(component, index) {
var added = false;
if(component) {
if(this.componentTypes == null ||
(OpenLayers.Util.indexOf(this.componentTypes,
component.CLASS_NAME) > -1)) {
if(index != null && (index < this.components.length)) {
var components1 = this.components.slice(0, index);
var components2 = this.components.slice(index,
this.components.length);
components1.push(component);
this.components = components1.concat(components2);
} else {
this.components.push(component);
}
component.parent = this;
this.clearBounds();
added = true;
}
}
return added;
},
/**
* APIMethod: removeComponents
* Remove components from this geometry.
*
* Parameters:
* components - {Array(<OpenLayers.Geometry>)} The components to be removed
*
* Returns:
* {Boolean} A component was removed.
*/
removeComponents: function(components) {
var removed = false;
if(!(OpenLayers.Util.isArray(components))) {
components = [components];
}
for(var i=components.length-1; i>=0; --i) {
removed = this.removeComponent(components[i]) || removed;
}
return removed;
},
/**
* Method: removeComponent
* Remove a component from this geometry.
*
* Parameters:
* component - {<OpenLayers.Geometry>}
*
* Returns:
* {Boolean} The component was removed.
*/
removeComponent: function(component) {
OpenLayers.Util.removeItem(this.components, component);
// clearBounds() so that it gets recalculated on the next call
// to this.getBounds();
this.clearBounds();
return true;
},
/**
* APIMethod: getLength
* Calculate the length of this geometry
*
* Returns:
* {Float} The length of the geometry
*/
getLength: function() {
var length = 0.0;
for (var i=0, len=this.components.length; i<len; i++) {
length += this.components[i].getLength();
}
return length;
},
/**
* APIMethod: getArea
* Calculate the area of this geometry. Note how this function is overridden
* in <OpenLayers.Geometry.Polygon>.
*
* Returns:
* {Float} The area of the collection by summing its parts
*/
getArea: function() {
var area = 0.0;
for (var i=0, len=this.components.length; i<len; i++) {
area += this.components[i].getArea();
}
return area;
},
/**
* APIMethod: getGeodesicArea
* Calculate the approximate area of the polygon were it projected onto
* the earth.
*
* Parameters:
* projection - {<OpenLayers.Projection>} The spatial reference system
* for the geometry coordinates. If not provided, Geographic/WGS84 is
* assumed.
*
* Reference:
* Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
* Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
* Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
*
* Returns:
* {float} The approximate geodesic area of the geometry in square meters.
*/
getGeodesicArea: function(projection) {
var area = 0.0;
for(var i=0, len=this.components.length; i<len; i++) {
area += this.components[i].getGeodesicArea(projection);
}
return area;
},
/**
* APIMethod: getCentroid
*
* Compute the centroid for this geometry collection.
*
* Parameters:
* weighted - {Boolean} Perform the getCentroid computation recursively,
* returning an area weighted average of all geometries in this collection.
*
* Returns:
* {<OpenLayers.Geometry.Point>} The centroid of the collection
*/
getCentroid: function(weighted) {
if (!weighted) {
return this.components.length && this.components[0].getCentroid();
}
var len = this.components.length;
if (!len) {
return false;
}
var areas = [];
var centroids = [];
var areaSum = 0;
var minArea = Number.MAX_VALUE;
var component;
for (var i=0; i<len; ++i) {
component = this.components[i];
var area = component.getArea();
var centroid = component.getCentroid(true);
if (isNaN(area) || isNaN(centroid.x) || isNaN(centroid.y)) {
continue;
}
areas.push(area);
areaSum += area;
minArea = (area < minArea && area > 0) ? area : minArea;
centroids.push(centroid);
}
len = areas.length;
if (areaSum === 0) {
// all the components in this collection have 0 area
// probably a collection of points -- weight all the points the same
for (var i=0; i<len; ++i) {
areas[i] = 1;
}
areaSum = areas.length;
} else {
// normalize all the areas where the smallest area will get
// a value of 1
for (var i=0; i<len; ++i) {
areas[i] /= minArea;
}
areaSum /= minArea;
}
var xSum = 0, ySum = 0, centroid, area;
for (var i=0; i<len; ++i) {
centroid = centroids[i];
area = areas[i];
xSum += centroid.x * area;
ySum += centroid.y * area;
}
return new OpenLayers.Geometry.Point(xSum/areaSum, ySum/areaSum);
},
/**
* APIMethod: getGeodesicLength
* Calculate the approximate length of the geometry were it projected onto
* the earth.
*
* projection - {<OpenLayers.Projection>} The spatial reference system
* for the geometry coordinates. If not provided, Geographic/WGS84 is
* assumed.
*
* Returns:
* {Float} The appoximate geodesic length of the geometry in meters.
*/
getGeodesicLength: function(projection) {
var length = 0.0;
for(var i=0, len=this.components.length; i<len; i++) {
length += this.components[i].getGeodesicLength(projection);
}
return length;
},
/**
* APIMethod: move
* Moves a geometry by the given displacement along positive x and y axes.
* This modifies the position of the geometry and clears the cached
* bounds.
*
* Parameters:
* x - {Float} Distance to move geometry in positive x direction.
* y - {Float} Distance to move geometry in positive y direction.
*/
move: function(x, y) {
for(var i=0, len=this.components.length; i<len; i++) {
this.components[i].move(x, y);
}
},
/**
* APIMethod: rotate
* Rotate a geometry around some origin
*
* Parameters:
* angle - {Float} Rotation angle in degrees (measured counterclockwise
* from the positive x-axis)
* origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
*/
rotate: function(angle, origin) {
for(var i=0, len=this.components.length; i<len; ++i) {
this.components[i].rotate(angle, origin);
}
},
/**
* APIMethod: resize
* Resize a geometry relative to some origin. Use this method to apply
* a uniform scaling to a geometry.
*
* Parameters:
* scale - {Float} Factor by which to scale the geometry. A scale of 2
* doubles the size of the geometry in each dimension
* (lines, for example, will be twice as long, and polygons
* will have four times the area).
* origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
* ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.
*
* Returns:
* {<OpenLayers.Geometry>} - The current geometry.
*/
resize: function(scale, origin, ratio) {
for(var i=0; i<this.components.length; ++i) {
this.components[i].resize(scale, origin, ratio);
}
return this;
},
/**
* APIMethod: distanceTo
* Calculate the closest distance between two geometries (on the x-y plane).
*
* Parameters:
* geometry - {<OpenLayers.Geometry>} The target geometry.
* options - {Object} Optional properties for configuring the distance
* calculation.
*
* Valid options:
* details - {Boolean} Return details from the distance calculation.
* Default is false.
* edge - {Boolean} Calculate the distance from this geometry to the
* nearest edge of the target geometry. Default is true. If true,
* calling distanceTo from a geometry that is wholly contained within
* the target will result in a non-zero distance. If false, whenever
* geometries intersect, calling distanceTo will return 0. If false,
* details cannot be returned.
*
* Returns:
* {Number | Object} The distance between this geometry and the target.
* If details is true, the return will be an object with distance,
* x0, y0, x1, and y1 properties. The x0 and y0 properties represent
* the coordinates of the closest point on this geometry. The x1 and y1
* properties represent the coordinates of the closest point on the
* target geometry.
*/
distanceTo: function(geometry, options) {
var edge = !(options && options.edge === false);
var details = edge && options && options.details;
var result, best, distance;
var min = Number.POSITIVE_INFINITY;
for(var i=0, len=this.components.length; i<len; ++i) {
result = this.components[i].distanceTo(geometry, options);
distance = details ? result.distance : result;
if(distance < min) {
min = distance;
best = result;
if(min == 0) {
break;
}
}
}
return best;
},
/**
* APIMethod: equals
* Determine whether another geometry is equivalent to this one. Geometries
* are considered equivalent if all components have the same coordinates.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>} The geometry to test.
*
* Returns:
* {Boolean} The supplied geometry is equivalent to this geometry.
*/
equals: function(geometry) {
var equivalent = true;
if(!geometry || !geometry.CLASS_NAME ||
(this.CLASS_NAME != geometry.CLASS_NAME)) {
equivalent = false;
} else if(!(OpenLayers.Util.isArray(geometry.components)) ||
(geometry.components.length != this.components.length)) {
equivalent = false;
} else {
for(var i=0, len=this.components.length; i<len; ++i) {
if(!this.components[i].equals(geometry.components[i])) {
equivalent = false;
break;
}
}
}
return equivalent;
},
/**
* APIMethod: transform
* Reproject the components geometry from source to dest.
*
* Parameters:
* source - {<OpenLayers.Projection>}
* dest - {<OpenLayers.Projection>}
*
* Returns:
* {<OpenLayers.Geometry>}
*/
transform: function(source, dest) {
if (source && dest) {
for (var i=0, len=this.components.length; i<len; i++) {
var component = this.components[i];
component.transform(source, dest);
}
this.bounds = null;
}
return this;
},
/**
* APIMethod: intersects
* Determine if the input geometry intersects this one.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>} Any type of geometry.
*
* Returns:
* {Boolean} The input geometry intersects this one.
*/
intersects: function(geometry) {
var intersect = false;
for(var i=0, len=this.components.length; i<len; ++ i) {
intersect = geometry.intersects(this.components[i]);
if(intersect) {
break;
}
}
return intersect;
},
/**
* APIMethod: getVertices
* Return a list of all points in this geometry.
*
* Parameters:
* nodes - {Boolean} For lines, only return vertices that are
* endpoints. If false, for lines, only vertices that are not
* endpoints will be returned. If not provided, all vertices will
* be returned.
*
* Returns:
* {Array} A list of all vertices in the geometry.
*/
getVertices: function(nodes) {
var vertices = [];
for(var i=0, len=this.components.length; i<len; ++i) {
Array.prototype.push.apply(
vertices, this.components[i].getVertices(nodes)
);
}
return vertices;
},
CLASS_NAME: "OpenLayers.Geometry.Collection"
});
/* ======================================================================
OpenLayers/Geometry/MultiPoint.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Geometry/Collection.js
* @requires OpenLayers/Geometry/Point.js
*/
/**
* Class: OpenLayers.Geometry.MultiPoint
* MultiPoint is a collection of Points. Create a new instance with the
* <OpenLayers.Geometry.MultiPoint> constructor.
*
* Inherits from:
* - <OpenLayers.Geometry.Collection>
* - <OpenLayers.Geometry>
*/
OpenLayers.Geometry.MultiPoint = OpenLayers.Class(
OpenLayers.Geometry.Collection, {
/**
* Property: componentTypes
* {Array(String)} An array of class names representing the types of
* components that the collection can include. A null value means the
* component types are not restricted.
*/
componentTypes: ["OpenLayers.Geometry.Point"],
/**
* Constructor: OpenLayers.Geometry.MultiPoint
* Create a new MultiPoint Geometry
*
* Parameters:
* components - {Array(<OpenLayers.Geometry.Point>)}
*
* Returns:
* {<OpenLayers.Geometry.MultiPoint>}
*/
/**
* APIMethod: addPoint
* Wrapper for <OpenLayers.Geometry.Collection.addComponent>
*
* Parameters:
* point - {<OpenLayers.Geometry.Point>} Point to be added
* index - {Integer} Optional index
*/
addPoint: function(point, index) {
this.addComponent(point, index);
},
/**
* APIMethod: removePoint
* Wrapper for <OpenLayers.Geometry.Collection.removeComponent>
*
* Parameters:
* point - {<OpenLayers.Geometry.Point>} Point to be removed
*/
removePoint: function(point){
this.removeComponent(point);
},
CLASS_NAME: "OpenLayers.Geometry.MultiPoint"
});
/* ======================================================================
OpenLayers/Geometry/Curve.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Geometry/MultiPoint.js
*/
/**
* Class: OpenLayers.Geometry.Curve
* A Curve is a MultiPoint, whose points are assumed to be connected. To
* this end, we provide a "getLength()" function, which iterates through
* the points, summing the distances between them.
*
* Inherits:
* - <OpenLayers.Geometry.MultiPoint>
*/
OpenLayers.Geometry.Curve = OpenLayers.Class(OpenLayers.Geometry.MultiPoint, {
/**
* Property: componentTypes
* {Array(String)} An array of class names representing the types of
* components that the collection can include. A null
* value means the component types are not restricted.
*/
componentTypes: ["OpenLayers.Geometry.Point"],
/**
* Constructor: OpenLayers.Geometry.Curve
*
* Parameters:
* point - {Array(<OpenLayers.Geometry.Point>)}
*/
/**
* APIMethod: getLength
*
* Returns:
* {Float} The length of the curve
*/
getLength: function() {
var length = 0.0;
if ( this.components && (this.components.length > 1)) {
for(var i=1, len=this.components.length; i<len; i++) {
length += this.components[i-1].distanceTo(this.components[i]);
}
}
return length;
},
/**
* APIMethod: getGeodesicLength
* Calculate the approximate length of the geometry were it projected onto
* the earth.
*
* projection - {<OpenLayers.Projection>} The spatial reference system
* for the geometry coordinates. If not provided, Geographic/WGS84 is
* assumed.
*
* Returns:
* {Float} The appoximate geodesic length of the geometry in meters.
*/
getGeodesicLength: function(projection) {
var geom = this; // so we can work with a clone if needed
if(projection) {
var gg = new OpenLayers.Projection("EPSG:4326");
if(!gg.equals(projection)) {
geom = this.clone().transform(projection, gg);
}
}
var length = 0.0;
if(geom.components && (geom.components.length > 1)) {
var p1, p2;
for(var i=1, len=geom.components.length; i<len; i++) {
p1 = geom.components[i-1];
p2 = geom.components[i];
// this returns km and requires lon/lat properties
length += OpenLayers.Util.distVincenty(
{lon: p1.x, lat: p1.y}, {lon: p2.x, lat: p2.y}
);
}
}
// convert to m
return length * 1000;
},
CLASS_NAME: "OpenLayers.Geometry.Curve"
});
/* ======================================================================
OpenLayers/Geometry/LineString.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Geometry/Curve.js
*/
/**
* Class: OpenLayers.Geometry.LineString
* A LineString is a Curve which, once two points have been added to it, can
* never be less than two points long.
*
* Inherits from:
* - <OpenLayers.Geometry.Curve>
*/
OpenLayers.Geometry.LineString = OpenLayers.Class(OpenLayers.Geometry.Curve, {
/**
* Constructor: OpenLayers.Geometry.LineString
* Create a new LineString geometry
*
* Parameters:
* points - {Array(<OpenLayers.Geometry.Point>)} An array of points used to
* generate the linestring
*
*/
/**
* APIMethod: removeComponent
* Only allows removal of a point if there are three or more points in
* the linestring. (otherwise the result would be just a single point)
*
* Parameters:
* point - {<OpenLayers.Geometry.Point>} The point to be removed
*
* Returns:
* {Boolean} The component was removed.
*/
removeComponent: function(point) {
var removed = this.components && (this.components.length > 2);
if (removed) {
OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,
arguments);
}
return removed;
},
/**
* APIMethod: intersects
* Test for instersection between two geometries. This is a cheapo
* implementation of the Bently-Ottmann algorigithm. It doesn't
* really keep track of a sweep line data structure. It is closer
* to the brute force method, except that segments are sorted and
* potential intersections are only calculated when bounding boxes
* intersect.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
*
* Returns:
* {Boolean} The input geometry intersects this geometry.
*/
intersects: function(geometry) {
var intersect = false;
var type = geometry.CLASS_NAME;
if(type == "OpenLayers.Geometry.LineString" ||
type == "OpenLayers.Geometry.LinearRing" ||
type == "OpenLayers.Geometry.Point") {
var segs1 = this.getSortedSegments();
var segs2;
if(type == "OpenLayers.Geometry.Point") {
segs2 = [{
x1: geometry.x, y1: geometry.y,
x2: geometry.x, y2: geometry.y
}];
} else {
segs2 = geometry.getSortedSegments();
}
var seg1, seg1x1, seg1x2, seg1y1, seg1y2,
seg2, seg2y1, seg2y2;
// sweep right
outer: for(var i=0, len=segs1.length; i<len; ++i) {
seg1 = segs1[i];
seg1x1 = seg1.x1;
seg1x2 = seg1.x2;
seg1y1 = seg1.y1;
seg1y2 = seg1.y2;
inner: for(var j=0, jlen=segs2.length; j<jlen; ++j) {
seg2 = segs2[j];
if(seg2.x1 > seg1x2) {
// seg1 still left of seg2
break;
}
if(seg2.x2 < seg1x1) {
// seg2 still left of seg1
continue;
}
seg2y1 = seg2.y1;
seg2y2 = seg2.y2;
if(Math.min(seg2y1, seg2y2) > Math.max(seg1y1, seg1y2)) {
// seg2 above seg1
continue;
}
if(Math.max(seg2y1, seg2y2) < Math.min(seg1y1, seg1y2)) {
// seg2 below seg1
continue;
}
if(OpenLayers.Geometry.segmentsIntersect(seg1, seg2)) {
intersect = true;
break outer;
}
}
}
} else {
intersect = geometry.intersects(this);
}
return intersect;
},
/**
* Method: getSortedSegments
*
* Returns:
* {Array} An array of segment objects. Segment objects have properties
* x1, y1, x2, and y2. The start point is represented by x1 and y1.
* The end point is represented by x2 and y2. Start and end are
* ordered so that x1 < x2.
*/
getSortedSegments: function() {
var numSeg = this.components.length - 1;
var segments = new Array(numSeg), point1, point2;
for(var i=0; i<numSeg; ++i) {
point1 = this.components[i];
point2 = this.components[i + 1];
if(point1.x < point2.x) {
segments[i] = {
x1: point1.x,
y1: point1.y,
x2: point2.x,
y2: point2.y
};
} else {
segments[i] = {
x1: point2.x,
y1: point2.y,
x2: point1.x,
y2: point1.y
};
}
}
// more efficient to define this somewhere static
function byX1(seg1, seg2) {
return seg1.x1 - seg2.x1;
}
return segments.sort(byX1);
},
/**
* Method: splitWithSegment
* Split this geometry with the given segment.
*
* Parameters:
* seg - {Object} An object with x1, y1, x2, and y2 properties referencing
* segment endpoint coordinates.
* options - {Object} Properties of this object will be used to determine
* how the split is conducted.
*
* Valid options:
* edge - {Boolean} Allow splitting when only edges intersect. Default is
* true. If false, a vertex on the source segment must be within the
* tolerance distance of the intersection to be considered a split.
* tolerance - {Number} If a non-null value is provided, intersections
* within the tolerance distance of one of the source segment's
* endpoints will be assumed to occur at the endpoint.
*
* Returns:
* {Object} An object with *lines* and *points* properties. If the given
* segment intersects this linestring, the lines array will reference
* geometries that result from the split. The points array will contain
* all intersection points. Intersection points are sorted along the
* segment (in order from x1,y1 to x2,y2).
*/
splitWithSegment: function(seg, options) {
var edge = !(options && options.edge === false);
var tolerance = options && options.tolerance;
var lines = [];
var verts = this.getVertices();
var points = [];
var intersections = [];
var split = false;
var vert1, vert2, point;
var node, vertex, target;
var interOptions = {point: true, tolerance: tolerance};
var result = null;
for(var i=0, stop=verts.length-2; i<=stop; ++i) {
vert1 = verts[i];
points.push(vert1.clone());
vert2 = verts[i+1];
target = {x1: vert1.x, y1: vert1.y, x2: vert2.x, y2: vert2.y};
point = OpenLayers.Geometry.segmentsIntersect(
seg, target, interOptions
);
if(point instanceof OpenLayers.Geometry.Point) {
if((point.x === seg.x1 && point.y === seg.y1) ||
(point.x === seg.x2 && point.y === seg.y2) ||
point.equals(vert1) || point.equals(vert2)) {
vertex = true;
} else {
vertex = false;
}
if(vertex || edge) {
// push intersections different than the previous
if(!point.equals(intersections[intersections.length-1])) {
intersections.push(point.clone());
}
if(i === 0) {
if(point.equals(vert1)) {
continue;
}
}
if(point.equals(vert2)) {
continue;
}
split = true;
if(!point.equals(vert1)) {
points.push(point);
}
lines.push(new OpenLayers.Geometry.LineString(points));
points = [point.clone()];
}
}
}
if(split) {
points.push(vert2.clone());
lines.push(new OpenLayers.Geometry.LineString(points));
}
if(intersections.length > 0) {
// sort intersections along segment
var xDir = seg.x1 < seg.x2 ? 1 : -1;
var yDir = seg.y1 < seg.y2 ? 1 : -1;
result = {
lines: lines,
points: intersections.sort(function(p1, p2) {
return (xDir * p1.x - xDir * p2.x) || (yDir * p1.y - yDir * p2.y);
})
};
}
return result;
},
/**
* Method: split
* Use this geometry (the source) to attempt to split a target geometry.
*
* Parameters:
* target - {<OpenLayers.Geometry>} The target geometry.
* options - {Object} Properties of this object will be used to determine
* how the split is conducted.
*
* Valid options:
* mutual - {Boolean} Split the source geometry in addition to the target
* geometry. Default is false.
* edge - {Boolean} Allow splitting when only edges intersect. Default is
* true. If false, a vertex on the source must be within the tolerance
* distance of the intersection to be considered a split.
* tolerance - {Number} If a non-null value is provided, intersections
* within the tolerance distance of an existing vertex on the source
* will be assumed to occur at the vertex.
*
* Returns:
* {Array} A list of geometries (of this same type as the target) that
* result from splitting the target with the source geometry. The
* source and target geometry will remain unmodified. If no split
* results, null will be returned. If mutual is true and a split
* results, return will be an array of two arrays - the first will be
* all geometries that result from splitting the source geometry and
* the second will be all geometries that result from splitting the
* target geometry.
*/
split: function(target, options) {
var results = null;
var mutual = options && options.mutual;
var sourceSplit, targetSplit, sourceParts, targetParts;
if(target instanceof OpenLayers.Geometry.LineString) {
var verts = this.getVertices();
var vert1, vert2, seg, splits, lines, point;
var points = [];
sourceParts = [];
for(var i=0, stop=verts.length-2; i<=stop; ++i) {
vert1 = verts[i];
vert2 = verts[i+1];
seg = {
x1: vert1.x, y1: vert1.y,
x2: vert2.x, y2: vert2.y
};
targetParts = targetParts || [target];
if(mutual) {
points.push(vert1.clone());
}
for(var j=0; j<targetParts.length; ++j) {
splits = targetParts[j].splitWithSegment(seg, options);
if(splits) {
// splice in new features
lines = splits.lines;
if(lines.length > 0) {
lines.unshift(j, 1);
Array.prototype.splice.apply(targetParts, lines);
j += lines.length - 2;
}
if(mutual) {
for(var k=0, len=splits.points.length; k<len; ++k) {
point = splits.points[k];
if(!point.equals(vert1)) {
points.push(point);
sourceParts.push(new OpenLayers.Geometry.LineString(points));
if(point.equals(vert2)) {
points = [];
} else {
points = [point.clone()];
}
}
}
}
}
}
}
if(mutual && sourceParts.length > 0 && points.length > 0) {
points.push(vert2.clone());
sourceParts.push(new OpenLayers.Geometry.LineString(points));
}
} else {
results = target.splitWith(this, options);
}
if(targetParts && targetParts.length > 1) {
targetSplit = true;
} else {
targetParts = [];
}
if(sourceParts && sourceParts.length > 1) {
sourceSplit = true;
} else {
sourceParts = [];
}
if(targetSplit || sourceSplit) {
if(mutual) {
results = [sourceParts, targetParts];
} else {
results = targetParts;
}
}
return results;
},
/**
* Method: splitWith
* Split this geometry (the target) with the given geometry (the source).
*
* Parameters:
* geometry - {<OpenLayers.Geometry>} A geometry used to split this
* geometry (the source).
* options - {Object} Properties of this object will be used to determine
* how the split is conducted.
*
* Valid options:
* mutual - {Boolean} Split the source geometry in addition to the target
* geometry. Default is false.
* edge - {Boolean} Allow splitting when only edges intersect. Default is
* true. If false, a vertex on the source must be within the tolerance
* distance of the intersection to be considered a split.
* tolerance - {Number} If a non-null value is provided, intersections
* within the tolerance distance of an existing vertex on the source
* will be assumed to occur at the vertex.
*
* Returns:
* {Array} A list of geometries (of this same type as the target) that
* result from splitting the target with the source geometry. The
* source and target geometry will remain unmodified. If no split
* results, null will be returned. If mutual is true and a split
* results, return will be an array of two arrays - the first will be
* all geometries that result from splitting the source geometry and
* the second will be all geometries that result from splitting the
* target geometry.
*/
splitWith: function(geometry, options) {
return geometry.split(this, options);
},
/**
* APIMethod: getVertices
* Return a list of all points in this geometry.
*
* Parameters:
* nodes - {Boolean} For lines, only return vertices that are
* endpoints. If false, for lines, only vertices that are not
* endpoints will be returned. If not provided, all vertices will
* be returned.
*
* Returns:
* {Array} A list of all vertices in the geometry.
*/
getVertices: function(nodes) {
var vertices;
if(nodes === true) {
vertices = [
this.components[0],
this.components[this.components.length-1]
];
} else if (nodes === false) {
vertices = this.components.slice(1, this.components.length-1);
} else {
vertices = this.components.slice();
}
return vertices;
},
/**
* APIMethod: distanceTo
* Calculate the closest distance between two geometries (on the x-y plane).
*
* Parameters:
* geometry - {<OpenLayers.Geometry>} The target geometry.
* options - {Object} Optional properties for configuring the distance
* calculation.
*
* Valid options:
* details - {Boolean} Return details from the distance calculation.
* Default is false.
* edge - {Boolean} Calculate the distance from this geometry to the
* nearest edge of the target geometry. Default is true. If true,
* calling distanceTo from a geometry that is wholly contained within
* the target will result in a non-zero distance. If false, whenever
* geometries intersect, calling distanceTo will return 0. If false,
* details cannot be returned.
*
* Returns:
* {Number | Object} The distance between this geometry and the target.
* If details is true, the return will be an object with distance,
* x0, y0, x1, and x2 properties. The x0 and y0 properties represent
* the coordinates of the closest point on this geometry. The x1 and y1
* properties represent the coordinates of the closest point on the
* target geometry.
*/
distanceTo: function(geometry, options) {
var edge = !(options && options.edge === false);
var details = edge && options && options.details;
var result, best = {};
var min = Number.POSITIVE_INFINITY;
if(geometry instanceof OpenLayers.Geometry.Point) {
var segs = this.getSortedSegments();
var x = geometry.x;
var y = geometry.y;
var seg;
for(var i=0, len=segs.length; i<len; ++i) {
seg = segs[i];
result = OpenLayers.Geometry.distanceToSegment(geometry, seg);
if(result.distance < min) {
min = result.distance;
if(details) {
best = {
distance: min,
x0: result.x, y0: result.y,
x1: x, y1: y,
index: i,
indexDistance: new OpenLayers.Geometry.Point(seg.x1, seg.y1).distanceTo(geometry)
};
} else {
best = min;
}
if(min === 0) {
break;
}
}
}
} else if(geometry instanceof OpenLayers.Geometry.LineString) {
var segs0 = this.getSortedSegments();
var segs1 = geometry.getSortedSegments();
var seg0, seg1, intersection, x0, y0;
var len1 = segs1.length;
var interOptions = {point: true};
outer: for(var i=0, len=segs0.length; i<len; ++i) {
seg0 = segs0[i];
x0 = seg0.x1;
y0 = seg0.y1;
for(var j=0; j<len1; ++j) {
seg1 = segs1[j];
intersection = OpenLayers.Geometry.segmentsIntersect(seg0, seg1, interOptions);
if(intersection) {
min = 0;
best = {
distance: 0,
x0: intersection.x, y0: intersection.y,
x1: intersection.x, y1: intersection.y
};
break outer;
} else {
result = OpenLayers.Geometry.distanceToSegment({x: x0, y: y0}, seg1);
if(result.distance < min) {
min = result.distance;
best = {
distance: min,
x0: x0, y0: y0,
x1: result.x, y1: result.y
};
}
}
}
}
if(!details) {
best = best.distance;
}
if(min !== 0) {
// check the final vertex in this line's sorted segments
if(seg0) {
result = geometry.distanceTo(
new OpenLayers.Geometry.Point(seg0.x2, seg0.y2),
options
);
var dist = details ? result.distance : result;
if(dist < min) {
if(details) {
best = {
distance: min,
x0: result.x1, y0: result.y1,
x1: result.x0, y1: result.y0
};
} else {
best = dist;
}
}
}
}
} else {
best = geometry.distanceTo(this, options);
// swap since target comes from this line
if(details) {
best = {
distance: best.distance,
x0: best.x1, y0: best.y1,
x1: best.x0, y1: best.y0
};
}
}
return best;
},
/**
* APIMethod: simplify
* This function will return a simplified LineString.
* Simplification is based on the Douglas-Peucker algorithm.
*
*
* Parameters:
* tolerance - {number} threshold for simplification in map units
*
* Returns:
* {OpenLayers.Geometry.LineString} the simplified LineString
*/
simplify: function(tolerance){
if (this && this !== null) {
var points = this.getVertices();
if (points.length < 3) {
return this;
}
var compareNumbers = function(a, b){
return (a-b);
};
/**
* Private function doing the Douglas-Peucker reduction
*/
var douglasPeuckerReduction = function(points, firstPoint, lastPoint, tolerance){
var maxDistance = 0;
var indexFarthest = 0;
for (var index = firstPoint, distance; index < lastPoint; index++) {
distance = perpendicularDistance(points[firstPoint], points[lastPoint], points[index]);
if (distance > maxDistance) {
maxDistance = distance;
indexFarthest = index;
}
}
if (maxDistance > tolerance && indexFarthest != firstPoint) {
//Add the largest point that exceeds the tolerance
pointIndexsToKeep.push(indexFarthest);
douglasPeuckerReduction(points, firstPoint, indexFarthest, tolerance);
douglasPeuckerReduction(points, indexFarthest, lastPoint, tolerance);
}
};
/**
* Private function calculating the perpendicular distance
* TODO: check whether OpenLayers.Geometry.LineString::distanceTo() is faster or slower
*/
var perpendicularDistance = function(point1, point2, point){
//Area = |(1/2)(x1y2 + x2y3 + x3y1 - x2y1 - x3y2 - x1y3)| *Area of triangle
//Base = v((x1-x2)²+(x1-x2)²) *Base of Triangle*
//Area = .5*Base*H *Solve for height
//Height = Area/.5/Base
var area = Math.abs(0.5 * (point1.x * point2.y + point2.x * point.y + point.x * point1.y - point2.x * point1.y - point.x * point2.y - point1.x * point.y));
var bottom = Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2));
var height = area / bottom * 2;
return height;
};
var firstPoint = 0;
var lastPoint = points.length - 1;
var pointIndexsToKeep = [];
//Add the first and last index to the keepers
pointIndexsToKeep.push(firstPoint);
pointIndexsToKeep.push(lastPoint);
//The first and the last point cannot be the same
while (points[firstPoint].equals(points[lastPoint])) {
lastPoint--;
//Addition: the first point not equal to first point in the LineString is kept as well
pointIndexsToKeep.push(lastPoint);
}
douglasPeuckerReduction(points, firstPoint, lastPoint, tolerance);
var returnPoints = [];
pointIndexsToKeep.sort(compareNumbers);
for (var index = 0; index < pointIndexsToKeep.length; index++) {
returnPoints.push(points[pointIndexsToKeep[index]]);
}
return new OpenLayers.Geometry.LineString(returnPoints);
}
else {
return this;
}
},
CLASS_NAME: "OpenLayers.Geometry.LineString"
});
/**
* Function: OpenLayers.Geometry.LineString.geodesic
*
* Parameters:
* interpolate - {function(number): OpenLayers.Geometry.Point} Interpolate
* function.
* transform - {function(OpenLayers.Geometry.Point): OpenLayers.Geometry.Point}
* Transform from longitude/latitude to projected coordinates.
* squaredTolerance - {number} Squared tolerance.
*
* Returns:
* {OpenLayers.Geometry.LineString}
*/
OpenLayers.Geometry.LineString.geodesic =
function(interpolate, transform, squaredTolerance) {
// FIXME reduce garbage generation
// FIXME optimize stack operations
var components = [];
var geoA = interpolate(0);
var geoB = interpolate(1);
var a = transform(geoA);
var b = transform(geoB);
var geoStack = [geoB, geoA];
var stack = [b, a];
var fractionStack = [1, 0];
var fractions = {};
var maxIterations = 1e5;
var geoM, m, fracA, fracB, fracM, key;
while (--maxIterations > 0 && fractionStack.length > 0) {
// Pop the a coordinate off the stack
fracA = fractionStack.pop();
geoA = geoStack.pop();
a = stack.pop();
// Add the a coordinate if it has not been added yet
key = fracA.toString();
if (!(key in fractions)) {
components.push(a);
fractions[key] = true;
}
// Pop the b coordinate off the stack
fracB = fractionStack.pop();
geoB = geoStack.pop();
b = stack.pop();
// Find the m point between the a and b coordinates
fracM = (fracA + fracB) / 2;
geoM = interpolate(fracM);
m = transform(geoM);
if (OpenLayers.Geometry.distanceSquaredToSegment(m, {x1: a.x, y1: a.y,
x2: b.x, y2: b.y}).distance < squaredTolerance) {
// If the m point is sufficiently close to the straight line, then
// we discard it. Just use the b coordinate and move on to the next
// line segment.
components.push(b);
key = fracB.toString();
fractions[key] = true;
} else {
// Otherwise, we need to subdivide the current line segment.
// Split it into two and push the two line segments onto the stack.
fractionStack.push(fracB, fracM, fracM, fracA);
stack.push(b, m, m, a);
geoStack.push(geoB, geoM, geoM, geoA);
}
}
return new OpenLayers.Geometry.LineString(components);
};
/**
* Function: OpenLayers.Geometry.LineString.geodesicMeridian
* Generate a meridian (line at constant longitude).
*
* Parameters:
* lon - {number} Longitude.
* lat1 - {number} Latitude 1.
* lat2 - {number} Latitude 2.
* projection - {OpenLayers.Projection} Projection.
* squaredTolerance - {number} Squared tolerance.
*
* Returns:
* {OpenLayers.Geometry.LineString} Line geometry for the meridian at <lon>.
*/
OpenLayers.Geometry.LineString.geodesicMeridian =
function(lon, lat1, lat2, projection, squaredTolerance) {
var epsg4326Projection = new OpenLayers.Projection('EPSG:4326');
return OpenLayers.Geometry.LineString.geodesic(
function(frac) {
return new OpenLayers.Geometry.Point(
lon, lat1 + ((lat2 - lat1) * frac));
},
function(point) {
return point.transform(epsg4326Projection, projection);
},
squaredTolerance
);
};
/**
* Function: OpenLayers.Geometry.LineString.geodesicParallel
* Generate a parallel (line at constant latitude).
*
* Parameters:
* lat - {number} Latitude.
* lon1 - {number} Longitude 1.
* lon2 - {number} Longitude 2.
* projection {OpenLayers.Projection} Projection.
* squaredTolerance - {number} Squared tolerance.
*
* Returns:
* {OpenLayers.Geometry.LineString} Line geometry for the parallel at <lat>.
*/
OpenLayers.Geometry.LineString.geodesicParallel =
function(lat, lon1, lon2, projection, squaredTolerance) {
var epsg4326Projection = new OpenLayers.Projection('EPSG:4326');
return OpenLayers.Geometry.LineString.geodesic(
function(frac) {
return new OpenLayers.Geometry.Point(
lon1 + ((lon2 - lon1) * frac), lat);
},
function(point) {
return point.transform(epsg4326Projection, projection);
},
squaredTolerance
);
};
/* ======================================================================
OpenLayers/Geometry/MultiLineString.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Geometry/Collection.js
* @requires OpenLayers/Geometry/LineString.js
*/
/**
* Class: OpenLayers.Geometry.MultiLineString
* A MultiLineString is a geometry with multiple <OpenLayers.Geometry.LineString>
* components.
*
* Inherits from:
* - <OpenLayers.Geometry.Collection>
* - <OpenLayers.Geometry>
*/
OpenLayers.Geometry.MultiLineString = OpenLayers.Class(
OpenLayers.Geometry.Collection, {
/**
* Property: componentTypes
* {Array(String)} An array of class names representing the types of
* components that the collection can include. A null value means the
* component types are not restricted.
*/
componentTypes: ["OpenLayers.Geometry.LineString"],
/**
* Constructor: OpenLayers.Geometry.MultiLineString
* Constructor for a MultiLineString Geometry.
*
* Parameters:
* components - {Array(<OpenLayers.Geometry.LineString>)}
*
*/
/**
* Method: split
* Use this geometry (the source) to attempt to split a target geometry.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>} The target geometry.
* options - {Object} Properties of this object will be used to determine
* how the split is conducted.
*
* Valid options:
* mutual - {Boolean} Split the source geometry in addition to the target
* geometry. Default is false.
* edge - {Boolean} Allow splitting when only edges intersect. Default is
* true. If false, a vertex on the source must be within the tolerance
* distance of the intersection to be considered a split.
* tolerance - {Number} If a non-null value is provided, intersections
* within the tolerance distance of an existing vertex on the source
* will be assumed to occur at the vertex.
*
* Returns:
* {Array} A list of geometries (of this same type as the target) that
* result from splitting the target with the source geometry. The
* source and target geometry will remain unmodified. If no split
* results, null will be returned. If mutual is true and a split
* results, return will be an array of two arrays - the first will be
* all geometries that result from splitting the source geometry and
* the second will be all geometries that result from splitting the
* target geometry.
*/
split: function(geometry, options) {
var results = null;
var mutual = options && options.mutual;
var splits, sourceLine, sourceLines, sourceSplit, targetSplit;
var sourceParts = [];
var targetParts = [geometry];
for(var i=0, len=this.components.length; i<len; ++i) {
sourceLine = this.components[i];
sourceSplit = false;
for(var j=0; j < targetParts.length; ++j) {
splits = sourceLine.split(targetParts[j], options);
if(splits) {
if(mutual) {
sourceLines = splits[0];
for(var k=0, klen=sourceLines.length; k<klen; ++k) {
if(k===0 && sourceParts.length) {
sourceParts[sourceParts.length-1].addComponent(
sourceLines[k]
);
} else {
sourceParts.push(
new OpenLayers.Geometry.MultiLineString([
sourceLines[k]
])
);
}
}
sourceSplit = true;
splits = splits[1];
}
if(splits.length) {
// splice in new target parts
splits.unshift(j, 1);
Array.prototype.splice.apply(targetParts, splits);
break;
}
}
}
if(!sourceSplit) {
// source line was not hit
if(sourceParts.length) {
// add line to existing multi
sourceParts[sourceParts.length-1].addComponent(
sourceLine.clone()
);
} else {
// create a fresh multi
sourceParts = [
new OpenLayers.Geometry.MultiLineString(
sourceLine.clone()
)
];
}
}
}
if(sourceParts && sourceParts.length > 1) {
sourceSplit = true;
} else {
sourceParts = [];
}
if(targetParts && targetParts.length > 1) {
targetSplit = true;
} else {
targetParts = [];
}
if(sourceSplit || targetSplit) {
if(mutual) {
results = [sourceParts, targetParts];
} else {
results = targetParts;
}
}
return results;
},
/**
* Method: splitWith
* Split this geometry (the target) with the given geometry (the source).
*
* Parameters:
* geometry - {<OpenLayers.Geometry>} A geometry used to split this
* geometry (the source).
* options - {Object} Properties of this object will be used to determine
* how the split is conducted.
*
* Valid options:
* mutual - {Boolean} Split the source geometry in addition to the target
* geometry. Default is false.
* edge - {Boolean} Allow splitting when only edges intersect. Default is
* true. If false, a vertex on the source must be within the tolerance
* distance of the intersection to be considered a split.
* tolerance - {Number} If a non-null value is provided, intersections
* within the tolerance distance of an existing vertex on the source
* will be assumed to occur at the vertex.
*
* Returns:
* {Array} A list of geometries (of this same type as the target) that
* result from splitting the target with the source geometry. The
* source and target geometry will remain unmodified. If no split
* results, null will be returned. If mutual is true and a split
* results, return will be an array of two arrays - the first will be
* all geometries that result from splitting the source geometry and
* the second will be all geometries that result from splitting the
* target geometry.
*/
splitWith: function(geometry, options) {
var results = null;
var mutual = options && options.mutual;
var splits, targetLine, sourceLines, sourceSplit, targetSplit, sourceParts, targetParts;
if(geometry instanceof OpenLayers.Geometry.LineString) {
targetParts = [];
sourceParts = [geometry];
for(var i=0, len=this.components.length; i<len; ++i) {
targetSplit = false;
targetLine = this.components[i];
for(var j=0; j<sourceParts.length; ++j) {
splits = sourceParts[j].split(targetLine, options);
if(splits) {
if(mutual) {
sourceLines = splits[0];
if(sourceLines.length) {
// splice in new source parts
sourceLines.unshift(j, 1);
Array.prototype.splice.apply(sourceParts, sourceLines);
j += sourceLines.length - 2;
}
splits = splits[1];
if(splits.length === 0) {
splits = [targetLine.clone()];
}
}
for(var k=0, klen=splits.length; k<klen; ++k) {
if(k===0 && targetParts.length) {
targetParts[targetParts.length-1].addComponent(
splits[k]
);
} else {
targetParts.push(
new OpenLayers.Geometry.MultiLineString([
splits[k]
])
);
}
}
targetSplit = true;
}
}
if(!targetSplit) {
// target component was not hit
if(targetParts.length) {
// add it to any existing multi-line
targetParts[targetParts.length-1].addComponent(
targetLine.clone()
);
} else {
// or start with a fresh multi-line
targetParts = [
new OpenLayers.Geometry.MultiLineString([
targetLine.clone()
])
];
}
}
}
} else {
results = geometry.split(this);
}
if(sourceParts && sourceParts.length > 1) {
sourceSplit = true;
} else {
sourceParts = [];
}
if(targetParts && targetParts.length > 1) {
targetSplit = true;
} else {
targetParts = [];
}
if(sourceSplit || targetSplit) {
if(mutual) {
results = [sourceParts, targetParts];
} else {
results = targetParts;
}
}
return results;
},
CLASS_NAME: "OpenLayers.Geometry.MultiLineString"
});
/* ======================================================================
OpenLayers/Geometry/LinearRing.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Geometry/LineString.js
*/
/**
* Class: OpenLayers.Geometry.LinearRing
*
* A Linear Ring is a special LineString which is closed. It closes itself
* automatically on every addPoint/removePoint by adding a copy of the first
* point as the last point.
*
* Also, as it is the first in the line family to close itself, a getArea()
* function is defined to calculate the enclosed area of the linearRing
*
* Inherits:
* - <OpenLayers.Geometry.LineString>
*/
OpenLayers.Geometry.LinearRing = OpenLayers.Class(
OpenLayers.Geometry.LineString, {
/**
* Property: componentTypes
* {Array(String)} An array of class names representing the types of
* components that the collection can include. A null
* value means the component types are not restricted.
*/
componentTypes: ["OpenLayers.Geometry.Point"],
/**
* Constructor: OpenLayers.Geometry.LinearRing
* Linear rings are constructed with an array of points. This array
* can represent a closed or open ring. If the ring is open (the last
* point does not equal the first point), the constructor will close
* the ring. If the ring is already closed (the last point does equal
* the first point), it will be left closed.
*
* Parameters:
* points - {Array(<OpenLayers.Geometry.Point>)} points
*/
/**
* APIMethod: addComponent
* Adds a point to geometry components. If the point is to be added to
* the end of the components array and it is the same as the last point
* already in that array, the duplicate point is not added. This has
* the effect of closing the ring if it is not already closed, and
* doing the right thing if it is already closed. This behavior can
* be overridden by calling the method with a non-null index as the
* second argument.
*
* Parameters:
* point - {<OpenLayers.Geometry.Point>}
* index - {Integer} Index into the array to insert the component
*
* Returns:
* {Boolean} Was the Point successfully added?
*/
addComponent: function(point, index) {
var added = false;
//remove last point
var lastPoint = this.components.pop();
// given an index, add the point
// without an index only add non-duplicate points
if(index != null || !point.equals(lastPoint)) {
added = OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,
arguments);
}
//append copy of first point
var firstPoint = this.components[0];
OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,
[firstPoint]);
return added;
},
/**
* APIMethod: removeComponent
* Removes a point from geometry components.
*
* Parameters:
* point - {<OpenLayers.Geometry.Point>}
*
* Returns:
* {Boolean} The component was removed.
*/
removeComponent: function(point) {
var removed = this.components && (this.components.length > 3);
if (removed) {
//remove last point
this.components.pop();
//remove our point
OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,
arguments);
//append copy of first point
var firstPoint = this.components[0];
OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,
[firstPoint]);
}
return removed;
},
/**
* APIMethod: move
* Moves a geometry by the given displacement along positive x and y axes.
* This modifies the position of the geometry and clears the cached
* bounds.
*
* Parameters:
* x - {Float} Distance to move geometry in positive x direction.
* y - {Float} Distance to move geometry in positive y direction.
*/
move: function(x, y) {
for(var i = 0, len=this.components.length; i<len - 1; i++) {
this.components[i].move(x, y);
}
},
/**
* APIMethod: rotate
* Rotate a geometry around some origin
*
* Parameters:
* angle - {Float} Rotation angle in degrees (measured counterclockwise
* from the positive x-axis)
* origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
*/
rotate: function(angle, origin) {
for(var i=0, len=this.components.length; i<len - 1; ++i) {
this.components[i].rotate(angle, origin);
}
},
/**
* APIMethod: resize
* Resize a geometry relative to some origin. Use this method to apply
* a uniform scaling to a geometry.
*
* Parameters:
* scale - {Float} Factor by which to scale the geometry. A scale of 2
* doubles the size of the geometry in each dimension
* (lines, for example, will be twice as long, and polygons
* will have four times the area).
* origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
* ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.
*
* Returns:
* {<OpenLayers.Geometry>} - The current geometry.
*/
resize: function(scale, origin, ratio) {
for(var i=0, len=this.components.length; i<len - 1; ++i) {
this.components[i].resize(scale, origin, ratio);
}
return this;
},
/**
* APIMethod: transform
* Reproject the components geometry from source to dest.
*
* Parameters:
* source - {<OpenLayers.Projection>}
* dest - {<OpenLayers.Projection>}
*
* Returns:
* {<OpenLayers.Geometry>}
*/
transform: function(source, dest) {
if (source && dest) {
for (var i=0, len=this.components.length; i<len - 1; i++) {
var component = this.components[i];
component.transform(source, dest);
}
this.bounds = null;
}
return this;
},
/**
* APIMethod: getCentroid
*
* Returns:
* {<OpenLayers.Geometry.Point>} The centroid of the collection
*/
getCentroid: function() {
if (this.components) {
var len = this.components.length;
if (len > 0 && len <= 2) {
return this.components[0].clone();
} else if (len > 2) {
var sumX = 0.0;
var sumY = 0.0;
var x0 = this.components[0].x;
var y0 = this.components[0].y;
var area = -1 * this.getArea();
if (area != 0) {
for (var i = 0; i < len - 1; i++) {
var b = this.components[i];
var c = this.components[i+1];
sumX += (b.x + c.x - 2 * x0) * ((b.x - x0) * (c.y - y0) - (c.x - x0) * (b.y - y0));
sumY += (b.y + c.y - 2 * y0) * ((b.x - x0) * (c.y - y0) - (c.x - x0) * (b.y - y0));
}
var x = x0 + sumX / (6 * area);
var y = y0 + sumY / (6 * area);
} else {
for (var i = 0; i < len - 1; i++) {
sumX += this.components[i].x;
sumY += this.components[i].y;
}
var x = sumX / (len - 1);
var y = sumY / (len - 1);
}
return new OpenLayers.Geometry.Point(x, y);
} else {
return null;
}
}
},
/**
* APIMethod: getArea
* Note - The area is positive if the ring is oriented CW, otherwise
* it will be negative.
*
* Returns:
* {Float} The signed area for a ring.
*/
getArea: function() {
var area = 0.0;
if ( this.components && (this.components.length > 2)) {
var sum = 0.0;
for (var i=0, len=this.components.length; i<len - 1; i++) {
var b = this.components[i];
var c = this.components[i+1];
sum += (b.x + c.x) * (c.y - b.y);
}
area = - sum / 2.0;
}
return area;
},
/**
* APIMethod: getGeodesicArea
* Calculate the approximate area of the polygon were it projected onto
* the earth. Note that this area will be positive if ring is oriented
* clockwise, otherwise it will be negative.
*
* Parameters:
* projection - {<OpenLayers.Projection>} The spatial reference system
* for the geometry coordinates. If not provided, Geographic/WGS84 is
* assumed.
*
* Reference:
* Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
* Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
* Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
*
* Returns:
* {float} The approximate signed geodesic area of the polygon in square
* meters.
*/
getGeodesicArea: function(projection) {
var ring = this; // so we can work with a clone if needed
if(projection) {
var gg = new OpenLayers.Projection("EPSG:4326");
if(!gg.equals(projection)) {
ring = this.clone().transform(projection, gg);
}
}
var area = 0.0;
var len = ring.components && ring.components.length;
if(len > 2) {
var p1, p2;
for(var i=0; i<len-1; i++) {
p1 = ring.components[i];
p2 = ring.components[i+1];
area += OpenLayers.Util.rad(p2.x - p1.x) *
(2 + Math.sin(OpenLayers.Util.rad(p1.y)) +
Math.sin(OpenLayers.Util.rad(p2.y)));
}
area = area * OpenLayers.Util.VincentyConstants.a * OpenLayers.Util.VincentyConstants.a / 2.0;
}
return area;
},
/**
* Method: containsPoint
* Test if a point is inside a linear ring. For the case where a point
* is coincident with a linear ring edge, returns 1. Otherwise,
* returns boolean.
*
* Parameters:
* point - {<OpenLayers.Geometry.Point>}
*
* Returns:
* {Boolean | Number} The point is inside the linear ring. Returns 1 if
* the point is coincident with an edge. Returns boolean otherwise.
*/
containsPoint: function(point) {
var approx = OpenLayers.Number.limitSigDigs;
var digs = 14;
var px = approx(point.x, digs);
var py = approx(point.y, digs);
function getX(y, x1, y1, x2, y2) {
return (y - y2) * ((x2 - x1) / (y2 - y1)) + x2;
}
var numSeg = this.components.length - 1;
var start, end, x1, y1, x2, y2, cx, cy;
var crosses = 0;
for(var i=0; i<numSeg; ++i) {
start = this.components[i];
x1 = approx(start.x, digs);
y1 = approx(start.y, digs);
end = this.components[i + 1];
x2 = approx(end.x, digs);
y2 = approx(end.y, digs);
/**
* The following conditions enforce five edge-crossing rules:
* 1. points coincident with edges are considered contained;
* 2. an upward edge includes its starting endpoint, and
* excludes its final endpoint;
* 3. a downward edge excludes its starting endpoint, and
* includes its final endpoint;
* 4. horizontal edges are excluded; and
* 5. the edge-ray intersection point must be strictly right
* of the point P.
*/
if(y1 == y2) {
// horizontal edge
if(py == y1) {
// point on horizontal line
if(x1 <= x2 && (px >= x1 && px <= x2) || // right or vert
x1 >= x2 && (px <= x1 && px >= x2)) { // left or vert
// point on edge
crosses = -1;
break;
}
}
// ignore other horizontal edges
continue;
}
cx = approx(getX(py, x1, y1, x2, y2), digs);
if(cx == px) {
// point on line
if(y1 < y2 && (py >= y1 && py <= y2) || // upward
y1 > y2 && (py <= y1 && py >= y2)) { // downward
// point on edge
crosses = -1;
break;
}
}
if(cx <= px) {
// no crossing to the right
continue;
}
if(x1 != x2 && (cx < Math.min(x1, x2) || cx > Math.max(x1, x2))) {
// no crossing
continue;
}
if(y1 < y2 && (py >= y1 && py < y2) || // upward
y1 > y2 && (py < y1 && py >= y2)) { // downward
++crosses;
}
}
var contained = (crosses == -1) ?
// on edge
1 :
// even (out) or odd (in)
!!(crosses & 1);
return contained;
},
/**
* APIMethod: intersects
* Determine if the input geometry intersects this one.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>} Any type of geometry.
*
* Returns:
* {Boolean} The input geometry intersects this one.
*/
intersects: function(geometry) {
var intersect = false;
if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
intersect = this.containsPoint(geometry);
} else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LineString") {
intersect = geometry.intersects(this);
} else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
intersect = OpenLayers.Geometry.LineString.prototype.intersects.apply(
this, [geometry]
);
} else {
// check for component intersections
for(var i=0, len=geometry.components.length; i<len; ++ i) {
intersect = geometry.components[i].intersects(this);
if(intersect) {
break;
}
}
}
return intersect;
},
/**
* APIMethod: getVertices
* Return a list of all points in this geometry.
*
* Parameters:
* nodes - {Boolean} For lines, only return vertices that are
* endpoints. If false, for lines, only vertices that are not
* endpoints will be returned. If not provided, all vertices will
* be returned.
*
* Returns:
* {Array} A list of all vertices in the geometry.
*/
getVertices: function(nodes) {
return (nodes === true) ? [] : this.components.slice(0, this.components.length-1);
},
CLASS_NAME: "OpenLayers.Geometry.LinearRing"
});
/* ======================================================================
OpenLayers/Geometry/Polygon.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Geometry/Collection.js
* @requires OpenLayers/Geometry/LinearRing.js
*/
/**
* Class: OpenLayers.Geometry.Polygon
* Polygon is a collection of Geometry.LinearRings.
*
* Inherits from:
* - <OpenLayers.Geometry.Collection>
* - <OpenLayers.Geometry>
*/
OpenLayers.Geometry.Polygon = OpenLayers.Class(
OpenLayers.Geometry.Collection, {
/**
* Property: componentTypes
* {Array(String)} An array of class names representing the types of
* components that the collection can include. A null value means the
* component types are not restricted.
*/
componentTypes: ["OpenLayers.Geometry.LinearRing"],
/**
* Constructor: OpenLayers.Geometry.Polygon
* Constructor for a Polygon geometry.
* The first ring (this.component[0])is the outer bounds of the polygon and
* all subsequent rings (this.component[1-n]) are internal holes.
*
*
* Parameters:
* components - {Array(<OpenLayers.Geometry.LinearRing>)}
*/
/**
* APIMethod: getArea
* Calculated by subtracting the areas of the internal holes from the
* area of the outer hole.
*
* Returns:
* {float} The area of the geometry
*/
getArea: function() {
var area = 0.0;
if ( this.components && (this.components.length > 0)) {
area += Math.abs(this.components[0].getArea());
for (var i=1, len=this.components.length; i<len; i++) {
area -= Math.abs(this.components[i].getArea());
}
}
return area;
},
/**
* APIMethod: getGeodesicArea
* Calculate the approximate area of the polygon were it projected onto
* the earth.
*
* Parameters:
* projection - {<OpenLayers.Projection>} The spatial reference system
* for the geometry coordinates. If not provided, Geographic/WGS84 is
* assumed.
*
* Reference:
* Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
* Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
* Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
*
* Returns:
* {float} The approximate geodesic area of the polygon in square meters.
*/
getGeodesicArea: function(projection) {
var area = 0.0;
if(this.components && (this.components.length > 0)) {
area += Math.abs(this.components[0].getGeodesicArea(projection));
for(var i=1, len=this.components.length; i<len; i++) {
area -= Math.abs(this.components[i].getGeodesicArea(projection));
}
}
return area;
},
/**
* Method: containsPoint
* Test if a point is inside a polygon. Points on a polygon edge are
* considered inside.
*
* Parameters:
* point - {<OpenLayers.Geometry.Point>}
*
* Returns:
* {Boolean | Number} The point is inside the polygon. Returns 1 if the
* point is on an edge. Returns boolean otherwise.
*/
containsPoint: function(point) {
var numRings = this.components.length;
var contained = false;
if(numRings > 0) {
// check exterior ring - 1 means on edge, boolean otherwise
contained = this.components[0].containsPoint(point);
if(contained !== 1) {
if(contained && numRings > 1) {
// check interior rings
var hole;
for(var i=1; i<numRings; ++i) {
hole = this.components[i].containsPoint(point);
if(hole) {
if(hole === 1) {
// on edge
contained = 1;
} else {
// in hole
contained = false;
}
break;
}
}
}
}
}
return contained;
},
/**
* APIMethod: intersects
* Determine if the input geometry intersects this one.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>} Any type of geometry.
*
* Returns:
* {Boolean} The input geometry intersects this one.
*/
intersects: function(geometry) {
var intersect = false;
var i, len;
if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
intersect = this.containsPoint(geometry);
} else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LineString" ||
geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
// check if rings/linestrings intersect
for(i=0, len=this.components.length; i<len; ++i) {
intersect = geometry.intersects(this.components[i]);
if(intersect) {
break;
}
}
if(!intersect) {
// check if this poly contains points of the ring/linestring
for(i=0, len=geometry.components.length; i<len; ++i) {
intersect = this.containsPoint(geometry.components[i]);
if(intersect) {
break;
}
}
}
} else {
for(i=0, len=geometry.components.length; i<len; ++ i) {
intersect = this.intersects(geometry.components[i]);
if(intersect) {
break;
}
}
}
// check case where this poly is wholly contained by another
if(!intersect && geometry.CLASS_NAME == "OpenLayers.Geometry.Polygon") {
// exterior ring points will be contained in the other geometry
var ring = this.components[0];
for(i=0, len=ring.components.length; i<len; ++i) {
intersect = geometry.containsPoint(ring.components[i]);
if(intersect) {
break;
}
}
}
return intersect;
},
/**
* APIMethod: distanceTo
* Calculate the closest distance between two geometries (on the x-y plane).
*
* Parameters:
* geometry - {<OpenLayers.Geometry>} The target geometry.
* options - {Object} Optional properties for configuring the distance
* calculation.
*
* Valid options:
* details - {Boolean} Return details from the distance calculation.
* Default is false.
* edge - {Boolean} Calculate the distance from this geometry to the
* nearest edge of the target geometry. Default is true. If true,
* calling distanceTo from a geometry that is wholly contained within
* the target will result in a non-zero distance. If false, whenever
* geometries intersect, calling distanceTo will return 0. If false,
* details cannot be returned.
*
* Returns:
* {Number | Object} The distance between this geometry and the target.
* If details is true, the return will be an object with distance,
* x0, y0, x1, and y1 properties. The x0 and y0 properties represent
* the coordinates of the closest point on this geometry. The x1 and y1
* properties represent the coordinates of the closest point on the
* target geometry.
*/
distanceTo: function(geometry, options) {
var edge = !(options && options.edge === false);
var result;
// this is the case where we might not be looking for distance to edge
if(!edge && this.intersects(geometry)) {
result = 0;
} else {
result = OpenLayers.Geometry.Collection.prototype.distanceTo.apply(
this, [geometry, options]
);
}
return result;
},
CLASS_NAME: "OpenLayers.Geometry.Polygon"
});
/**
* APIMethod: createRegularPolygon
* Create a regular polygon around a radius. Useful for creating circles
* and the like.
*
* Parameters:
* origin - {<OpenLayers.Geometry.Point>} center of polygon.
* radius - {Float} distance to vertex, in map units.
* sides - {Integer} Number of sides. 20 approximates a circle.
* rotation - {Float} original angle of rotation, in degrees.
*/
OpenLayers.Geometry.Polygon.createRegularPolygon = function(origin, radius, sides, rotation) {
var angle = Math.PI * ((1/sides) - (1/2));
if(rotation) {
angle += (rotation / 180) * Math.PI;
}
var rotatedAngle, x, y;
var points = [];
for(var i=0; i<sides; ++i) {
rotatedAngle = angle + (i * 2 * Math.PI / sides);
x = origin.x + (radius * Math.cos(rotatedAngle));
y = origin.y + (radius * Math.sin(rotatedAngle));
points.push(new OpenLayers.Geometry.Point(x, y));
}
var ring = new OpenLayers.Geometry.LinearRing(points);
return new OpenLayers.Geometry.Polygon([ring]);
};
/* ======================================================================
OpenLayers/Geometry/MultiPolygon.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Geometry/Collection.js
* @requires OpenLayers/Geometry/Polygon.js
*/
/**
* Class: OpenLayers.Geometry.MultiPolygon
* MultiPolygon is a geometry with multiple <OpenLayers.Geometry.Polygon>
* components. Create a new instance with the <OpenLayers.Geometry.MultiPolygon>
* constructor.
*
* Inherits from:
* - <OpenLayers.Geometry.Collection>
*/
OpenLayers.Geometry.MultiPolygon = OpenLayers.Class(
OpenLayers.Geometry.Collection, {
/**
* Property: componentTypes
* {Array(String)} An array of class names representing the types of
* components that the collection can include. A null value means the
* component types are not restricted.
*/
componentTypes: ["OpenLayers.Geometry.Polygon"],
/**
* Constructor: OpenLayers.Geometry.MultiPolygon
* Create a new MultiPolygon geometry
*
* Parameters:
* components - {Array(<OpenLayers.Geometry.Polygon>)} An array of polygons
* used to generate the MultiPolygon
*
*/
CLASS_NAME: "OpenLayers.Geometry.MultiPolygon"
});
/* ======================================================================
OpenLayers/Format/GML.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Format/XML.js
* @requires OpenLayers/Feature/Vector.js
* @requires OpenLayers/Geometry/Point.js
* @requires OpenLayers/Geometry/MultiPoint.js
* @requires OpenLayers/Geometry/LineString.js
* @requires OpenLayers/Geometry/MultiLineString.js
* @requires OpenLayers/Geometry/Polygon.js
* @requires OpenLayers/Geometry/MultiPolygon.js
*/
/**
* Class: OpenLayers.Format.GML
* Read/Write GML. Create a new instance with the <OpenLayers.Format.GML>
* constructor. Supports the GML simple features profile.
*
* Inherits from:
* - <OpenLayers.Format.XML>
*/
OpenLayers.Format.GML = OpenLayers.Class(OpenLayers.Format.XML, {
/**
* APIProperty: featureNS
* {String} Namespace used for feature attributes. Default is
* "http://mapserver.gis.umn.edu/mapserver".
*/
featureNS: "http://mapserver.gis.umn.edu/mapserver",
/**
* APIProperty: featurePrefix
* {String} Namespace alias (or prefix) for feature nodes. Default is
* "feature".
*/
featurePrefix: "feature",
/**
* APIProperty: featureName
* {String} Element name for features. Default is "featureMember".
*/
featureName: "featureMember",
/**
* APIProperty: layerName
* {String} Name of data layer. Default is "features".
*/
layerName: "features",
/**
* APIProperty: geometryName
* {String} Name of geometry element. Defaults to "geometry".
*/
geometryName: "geometry",
/**
* APIProperty: collectionName
* {String} Name of featureCollection element.
*/
collectionName: "FeatureCollection",
/**
* APIProperty: gmlns
* {String} GML Namespace.
*/
gmlns: "http://www.opengis.net/gml",
/**
* APIProperty: extractAttributes
* {Boolean} Extract attributes from GML.
*/
extractAttributes: true,
/**
* APIProperty: xy
* {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
* Changing is not recommended, a new Format should be instantiated.
*/
xy: true,
/**
* Constructor: OpenLayers.Format.GML
* Create a new parser for GML.
*
* Parameters:
* options - {Object} An optional object whose properties will be set on
* this instance.
*/
initialize: function(options) {
// compile regular expressions once instead of every time they are used
this.regExes = {
trimSpace: (/^\s*|\s*$/g),
removeSpace: (/\s*/g),
splitSpace: (/\s+/),
trimComma: (/\s*,\s*/g)
};
OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
},
/**
* APIMethod: read
* Read data from a string, and return a list of features.
*
* Parameters:
* data - {String} or {DOMElement} data to read/parse.
*
* Returns:
* {Array(<OpenLayers.Feature.Vector>)} An array of features.
*/
read: function(data) {
if(typeof data == "string") {
data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
}
var featureNodes = this.getElementsByTagNameNS(data.documentElement,
this.gmlns,
this.featureName);
var features = [];
for(var i=0; i<featureNodes.length; i++) {
var feature = this.parseFeature(featureNodes[i]);
if(feature) {
features.push(feature);
}
}
return features;
},
/**
* Method: parseFeature
* This function is the core of the GML parsing code in OpenLayers.
* It creates the geometries that are then attached to the returned
* feature, and calls parseAttributes() to get attribute data out.
*
* Parameters:
* node - {DOMElement} A GML feature node.
*/
parseFeature: function(node) {
// only accept one geometry per feature - look for highest "order"
var order = ["MultiPolygon", "Polygon",
"MultiLineString", "LineString",
"MultiPoint", "Point", "Envelope"];
// FIXME: In case we parse a feature with no geometry, but boundedBy an Envelope,
// this code creates a geometry derived from the Envelope. This is not correct.
var type, nodeList, geometry, parser;
for(var i=0; i<order.length; ++i) {
type = order[i];
nodeList = this.getElementsByTagNameNS(node, this.gmlns, type);
if(nodeList.length > 0) {
// only deal with first geometry of this type
parser = this.parseGeometry[type.toLowerCase()];
if(parser) {
geometry = parser.apply(this, [nodeList[0]]);
if (this.internalProjection && this.externalProjection) {
geometry.transform(this.externalProjection,
this.internalProjection);
}
} else {
throw new TypeError("Unsupported geometry type: " + type);
}
// stop looking for different geometry types
break;
}
}
var bounds;
var boxNodes = this.getElementsByTagNameNS(node, this.gmlns, "Box");
for(i=0; i<boxNodes.length; ++i) {
var boxNode = boxNodes[i];
var box = this.parseGeometry["box"].apply(this, [boxNode]);
var parentNode = boxNode.parentNode;
var parentName = parentNode.localName ||
parentNode.nodeName.split(":").pop();
if(parentName === "boundedBy") {
bounds = box;
} else {
geometry = box.toGeometry();
}
}
// construct feature (optionally with attributes)
var attributes;
if(this.extractAttributes) {
attributes = this.parseAttributes(node);
}
var feature = new OpenLayers.Feature.Vector(geometry, attributes);
feature.bounds = bounds;
var firstChild = this.getFirstElementChild(node);
feature.gml = {
featureType: firstChild.nodeName.split(":")[1],
featureNS: firstChild.namespaceURI,
featureNSPrefix: firstChild.prefix
};
feature.type = feature.gml.featureType;
// assign fid - this can come from a "fid" or "id" attribute
var childNode = node.firstChild;
var fid;
while(childNode) {
if(childNode.nodeType == 1) {
fid = childNode.getAttribute("fid") ||
childNode.getAttribute("id");
if(fid) {
break;
}
}
childNode = childNode.nextSibling;
}
feature.fid = fid;
return feature;
},
/**
* Property: parseGeometry
* Properties of this object are the functions that parse geometries based
* on their type.
*/
parseGeometry: {
/**
* Method: parseGeometry.point
* Given a GML node representing a point geometry, create an OpenLayers
* point geometry.
*
* Parameters:
* node - {DOMElement} A GML node.
*
* Returns:
* {<OpenLayers.Geometry.Point>} A point geometry.
*/
point: function(node) {
/**
* Three coordinate variations to consider:
* 1) <gml:pos>x y z</gml:pos>
* 2) <gml:coordinates>x, y, z</gml:coordinates>
* 3) <gml:coord><gml:X>x</gml:X><gml:Y>y</gml:Y></gml:coord>
*/
var nodeList, coordString;
var coords = [];
// look for <gml:pos>
var nodeList = this.getElementsByTagNameNS(node, this.gmlns, "pos");
if(nodeList.length > 0) {
coordString = nodeList[0].firstChild.nodeValue;
coordString = coordString.replace(this.regExes.trimSpace, "");
coords = coordString.split(this.regExes.splitSpace);
}
// look for <gml:coordinates>
if(coords.length == 0) {
nodeList = this.getElementsByTagNameNS(node, this.gmlns,
"coordinates");
if(nodeList.length > 0) {
coordString = nodeList[0].firstChild.nodeValue;
coordString = coordString.replace(this.regExes.removeSpace,
"");
coords = coordString.split(",");
}
}
// look for <gml:coord>
if(coords.length == 0) {
nodeList = this.getElementsByTagNameNS(node, this.gmlns,
"coord");
if(nodeList.length > 0) {
var xList = this.getElementsByTagNameNS(nodeList[0],
this.gmlns, "X");
var yList = this.getElementsByTagNameNS(nodeList[0],
this.gmlns, "Y");
if(xList.length > 0 && yList.length > 0) {
coords = [xList[0].firstChild.nodeValue,
yList[0].firstChild.nodeValue];
}
}
}
// preserve third dimension
if(coords.length == 2) {
coords[2] = null;
}
if (this.xy) {
return new OpenLayers.Geometry.Point(coords[0], coords[1],
coords[2]);
}
else{
return new OpenLayers.Geometry.Point(coords[1], coords[0],
coords[2]);
}
},
/**
* Method: parseGeometry.multipoint
* Given a GML node representing a multipoint geometry, create an
* OpenLayers multipoint geometry.
*
* Parameters:
* node - {DOMElement} A GML node.
*
* Returns:
* {<OpenLayers.Geometry.MultiPoint>} A multipoint geometry.
*/
multipoint: function(node) {
var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
"Point");
var components = [];
if(nodeList.length > 0) {
var point;
for(var i=0; i<nodeList.length; ++i) {
point = this.parseGeometry.point.apply(this, [nodeList[i]]);
if(point) {
components.push(point);
}
}
}
return new OpenLayers.Geometry.MultiPoint(components);
},
/**
* Method: parseGeometry.linestring
* Given a GML node representing a linestring geometry, create an
* OpenLayers linestring geometry.
*
* Parameters:
* node - {DOMElement} A GML node.
*
* Returns:
* {<OpenLayers.Geometry.LineString>} A linestring geometry.
*/
linestring: function(node, ring) {
/**
* Two coordinate variations to consider:
* 1) <gml:posList dimension="d">x0 y0 z0 x1 y1 z1</gml:posList>
* 2) <gml:coordinates>x0, y0, z0 x1, y1, z1</gml:coordinates>
*/
var nodeList, coordString;
var coords = [];
var points = [];
// look for <gml:posList>
nodeList = this.getElementsByTagNameNS(node, this.gmlns, "posList");
if(nodeList.length > 0) {
coordString = this.getChildValue(nodeList[0]);
coordString = coordString.replace(this.regExes.trimSpace, "");
coords = coordString.split(this.regExes.splitSpace);
var dim = parseInt(nodeList[0].getAttribute("dimension"));
var j, x, y, z;
for(var i=0; i<coords.length/dim; ++i) {
j = i * dim;
x = coords[j];
y = coords[j+1];
z = (dim == 2) ? null : coords[j+2];
if (this.xy) {
points.push(new OpenLayers.Geometry.Point(x, y, z));
} else {
points.push(new OpenLayers.Geometry.Point(y, x, z));
}
}
}
// look for <gml:coordinates>
if(coords.length == 0) {
nodeList = this.getElementsByTagNameNS(node, this.gmlns,
"coordinates");
if(nodeList.length > 0) {
coordString = this.getChildValue(nodeList[0]);
coordString = coordString.replace(this.regExes.trimSpace,
"");
coordString = coordString.replace(this.regExes.trimComma,
",");
var pointList = coordString.split(this.regExes.splitSpace);
for(var i=0; i<pointList.length; ++i) {
coords = pointList[i].split(",");
if(coords.length == 2) {
coords[2] = null;
}
if (this.xy) {
points.push(new OpenLayers.Geometry.Point(coords[0],
coords[1],
coords[2]));
} else {
points.push(new OpenLayers.Geometry.Point(coords[1],
coords[0],
coords[2]));
}
}
}
}
var line = null;
if(points.length != 0) {
if(ring) {
line = new OpenLayers.Geometry.LinearRing(points);
} else {
line = new OpenLayers.Geometry.LineString(points);
}
}
return line;
},
/**
* Method: parseGeometry.multilinestring
* Given a GML node representing a multilinestring geometry, create an
* OpenLayers multilinestring geometry.
*
* Parameters:
* node - {DOMElement} A GML node.
*
* Returns:
* {<OpenLayers.Geometry.MultiLineString>} A multilinestring geometry.
*/
multilinestring: function(node) {
var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
"LineString");
var components = [];
if(nodeList.length > 0) {
var line;
for(var i=0; i<nodeList.length; ++i) {
line = this.parseGeometry.linestring.apply(this,
[nodeList[i]]);
if(line) {
components.push(line);
}
}
}
return new OpenLayers.Geometry.MultiLineString(components);
},
/**
* Method: parseGeometry.polygon
* Given a GML node representing a polygon geometry, create an
* OpenLayers polygon geometry.
*
* Parameters:
* node - {DOMElement} A GML node.
*
* Returns:
* {<OpenLayers.Geometry.Polygon>} A polygon geometry.
*/
polygon: function(node) {
var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
"LinearRing");
var components = [];
if(nodeList.length > 0) {
// this assumes exterior ring first, inner rings after
var ring;
for(var i=0; i<nodeList.length; ++i) {
ring = this.parseGeometry.linestring.apply(this,
[nodeList[i], true]);
if(ring) {
components.push(ring);
}
}
}
return new OpenLayers.Geometry.Polygon(components);
},
/**
* Method: parseGeometry.multipolygon
* Given a GML node representing a multipolygon geometry, create an
* OpenLayers multipolygon geometry.
*
* Parameters:
* node - {DOMElement} A GML node.
*
* Returns:
* {<OpenLayers.Geometry.MultiPolygon>} A multipolygon geometry.
*/
multipolygon: function(node) {
var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
"Polygon");
var components = [];
if(nodeList.length > 0) {
var polygon;
for(var i=0; i<nodeList.length; ++i) {
polygon = this.parseGeometry.polygon.apply(this,
[nodeList[i]]);
if(polygon) {
components.push(polygon);
}
}
}
return new OpenLayers.Geometry.MultiPolygon(components);
},
envelope: function(node) {
var components = [];
var coordString;
var envelope;
var lpoint = this.getElementsByTagNameNS(node, this.gmlns, "lowerCorner");
if (lpoint.length > 0) {
var coords = [];
if(lpoint.length > 0) {
coordString = lpoint[0].firstChild.nodeValue;
coordString = coordString.replace(this.regExes.trimSpace, "");
coords = coordString.split(this.regExes.splitSpace);
}
if(coords.length == 2) {
coords[2] = null;
}
if (this.xy) {
var lowerPoint = new OpenLayers.Geometry.Point(coords[0], coords[1],coords[2]);
} else {
var lowerPoint = new OpenLayers.Geometry.Point(coords[1], coords[0],coords[2]);
}
}
var upoint = this.getElementsByTagNameNS(node, this.gmlns, "upperCorner");
if (upoint.length > 0) {
var coords = [];
if(upoint.length > 0) {
coordString = upoint[0].firstChild.nodeValue;
coordString = coordString.replace(this.regExes.trimSpace, "");
coords = coordString.split(this.regExes.splitSpace);
}
if(coords.length == 2) {
coords[2] = null;
}
if (this.xy) {
var upperPoint = new OpenLayers.Geometry.Point(coords[0], coords[1],coords[2]);
} else {
var upperPoint = new OpenLayers.Geometry.Point(coords[1], coords[0],coords[2]);
}
}
if (lowerPoint && upperPoint) {
components.push(new OpenLayers.Geometry.Point(lowerPoint.x, lowerPoint.y));
components.push(new OpenLayers.Geometry.Point(upperPoint.x, lowerPoint.y));
components.push(new OpenLayers.Geometry.Point(upperPoint.x, upperPoint.y));
components.push(new OpenLayers.Geometry.Point(lowerPoint.x, upperPoint.y));
components.push(new OpenLayers.Geometry.Point(lowerPoint.x, lowerPoint.y));
var ring = new OpenLayers.Geometry.LinearRing(components);
envelope = new OpenLayers.Geometry.Polygon([ring]);
}
return envelope;
},
/**
* Method: parseGeometry.box
* Given a GML node representing a box geometry, create an
* OpenLayers.Bounds.
*
* Parameters:
* node - {DOMElement} A GML node.
*
* Returns:
* {<OpenLayers.Bounds>} A bounds representing the box.
*/
box: function(node) {
var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
"coordinates");
var coordString;
var coords, beginPoint = null, endPoint = null;
if (nodeList.length > 0) {
coordString = nodeList[0].firstChild.nodeValue;
coords = coordString.split(" ");
if (coords.length == 2) {
beginPoint = coords[0].split(",");
endPoint = coords[1].split(",");
}
}
if (beginPoint !== null && endPoint !== null) {
return new OpenLayers.Bounds(parseFloat(beginPoint[0]),
parseFloat(beginPoint[1]),
parseFloat(endPoint[0]),
parseFloat(endPoint[1]) );
}
}
},
/**
* Method: parseAttributes
*
* Parameters:
* node - {DOMElement}
*
* Returns:
* {Object} An attributes object.
*/
parseAttributes: function(node) {
var attributes = {};
// assume attributes are children of the first type 1 child
var childNode = node.firstChild;
var children, i, child, grandchildren, grandchild, name, value;
while(childNode) {
if(childNode.nodeType == 1) {
// attributes are type 1 children with one type 3 child
children = childNode.childNodes;
for(i=0; i<children.length; ++i) {
child = children[i];
if(child.nodeType == 1) {
grandchildren = child.childNodes;
if(grandchildren.length == 1) {
grandchild = grandchildren[0];
if(grandchild.nodeType == 3 ||
grandchild.nodeType == 4) {
name = (child.prefix) ?
child.nodeName.split(":")[1] :
child.nodeName;
value = grandchild.nodeValue.replace(
this.regExes.trimSpace, "");
attributes[name] = value;
}
} else {
// If child has no childNodes (grandchildren),
// set an attribute with null value.
// e.g. <prefix:fieldname/> becomes
// {fieldname: null}
attributes[child.nodeName.split(":").pop()] = null;
}
}
}
break;
}
childNode = childNode.nextSibling;
}
return attributes;
},
/**
* APIMethod: write
* Generate a GML document string given a list of features.
*
* Parameters:
* features - {Array(<OpenLayers.Feature.Vector>)} List of features to
* serialize into a string.
*
* Returns:
* {String} A string representing the GML document.
*/
write: function(features) {
if(!(OpenLayers.Util.isArray(features))) {
features = [features];
}
var gml = this.createElementNS("http://www.opengis.net/wfs",
"wfs:" + this.collectionName);
for(var i=0; i<features.length; i++) {
gml.appendChild(this.createFeatureXML(features[i]));
}
return OpenLayers.Format.XML.prototype.write.apply(this, [gml]);
},
/**
* Method: createFeatureXML
* Accept an OpenLayers.Feature.Vector, and build a GML node for it.
*
* Parameters:
* feature - {<OpenLayers.Feature.Vector>} The feature to be built as GML.
*
* Returns:
* {DOMElement} A node reprensting the feature in GML.
*/
createFeatureXML: function(feature) {
var geometry = feature.geometry;
var geometryNode = this.buildGeometryNode(geometry);
var geomContainer = this.createElementNS(this.featureNS,
this.featurePrefix + ":" +
this.geometryName);
geomContainer.appendChild(geometryNode);
var featureNode = this.createElementNS(this.gmlns,
"gml:" + this.featureName);
var featureContainer = this.createElementNS(this.featureNS,
this.featurePrefix + ":" +
this.layerName);
var fid = feature.fid || feature.id;
featureContainer.setAttribute("fid", fid);
featureContainer.appendChild(geomContainer);
for(var attr in feature.attributes) {
var attrText = this.createTextNode(feature.attributes[attr]);
var nodename = attr.substring(attr.lastIndexOf(":") + 1);
var attrContainer = this.createElementNS(this.featureNS,
this.featurePrefix + ":" +
nodename);
attrContainer.appendChild(attrText);
featureContainer.appendChild(attrContainer);
}
featureNode.appendChild(featureContainer);
return featureNode;
},
/**
* APIMethod: buildGeometryNode
*/
buildGeometryNode: function(geometry) {
if (this.externalProjection && this.internalProjection) {
geometry = geometry.clone();
geometry.transform(this.internalProjection,
this.externalProjection);
}
var className = geometry.CLASS_NAME;
var type = className.substring(className.lastIndexOf(".") + 1);
var builder = this.buildGeometry[type.toLowerCase()];
return builder.apply(this, [geometry]);
},
/**
* Property: buildGeometry
* Object containing methods to do the actual geometry node building
* based on geometry type.
*/
buildGeometry: {
// TBD retrieve the srs from layer
// srsName is non-standard, so not including it until it's right.
// gml.setAttribute("srsName",
// "http://www.opengis.net/gml/srs/epsg.xml#4326");
/**
* Method: buildGeometry.point
* Given an OpenLayers point geometry, create a GML point.
*
* Parameters:
* geometry - {<OpenLayers.Geometry.Point>} A point geometry.
*
* Returns:
* {DOMElement} A GML point node.
*/
point: function(geometry) {
var gml = this.createElementNS(this.gmlns, "gml:Point");
gml.appendChild(this.buildCoordinatesNode(geometry));
return gml;
},
/**
* Method: buildGeometry.multipoint
* Given an OpenLayers multipoint geometry, create a GML multipoint.
*
* Parameters:
* geometry - {<OpenLayers.Geometry.MultiPoint>} A multipoint geometry.
*
* Returns:
* {DOMElement} A GML multipoint node.
*/
multipoint: function(geometry) {
var gml = this.createElementNS(this.gmlns, "gml:MultiPoint");
var points = geometry.components;
var pointMember, pointGeom;
for(var i=0; i<points.length; i++) {
pointMember = this.createElementNS(this.gmlns,
"gml:pointMember");
pointGeom = this.buildGeometry.point.apply(this,
[points[i]]);
pointMember.appendChild(pointGeom);
gml.appendChild(pointMember);
}
return gml;
},
/**
* Method: buildGeometry.linestring
* Given an OpenLayers linestring geometry, create a GML linestring.
*
* Parameters:
* geometry - {<OpenLayers.Geometry.LineString>} A linestring geometry.
*
* Returns:
* {DOMElement} A GML linestring node.
*/
linestring: function(geometry) {
var gml = this.createElementNS(this.gmlns, "gml:LineString");
gml.appendChild(this.buildCoordinatesNode(geometry));
return gml;
},
/**
* Method: buildGeometry.multilinestring
* Given an OpenLayers multilinestring geometry, create a GML
* multilinestring.
*
* Parameters:
* geometry - {<OpenLayers.Geometry.MultiLineString>} A multilinestring
* geometry.
*
* Returns:
* {DOMElement} A GML multilinestring node.
*/
multilinestring: function(geometry) {
var gml = this.createElementNS(this.gmlns, "gml:MultiLineString");
var lines = geometry.components;
var lineMember, lineGeom;
for(var i=0; i<lines.length; ++i) {
lineMember = this.createElementNS(this.gmlns,
"gml:lineStringMember");
lineGeom = this.buildGeometry.linestring.apply(this,
[lines[i]]);
lineMember.appendChild(lineGeom);
gml.appendChild(lineMember);
}
return gml;
},
/**
* Method: buildGeometry.linearring
* Given an OpenLayers linearring geometry, create a GML linearring.
*
* Parameters:
* geometry - {<OpenLayers.Geometry.LinearRing>} A linearring geometry.
*
* Returns:
* {DOMElement} A GML linearring node.
*/
linearring: function(geometry) {
var gml = this.createElementNS(this.gmlns, "gml:LinearRing");
gml.appendChild(this.buildCoordinatesNode(geometry));
return gml;
},
/**
* Method: buildGeometry.polygon
* Given an OpenLayers polygon geometry, create a GML polygon.
*
* Parameters:
* geometry - {<OpenLayers.Geometry.Polygon>} A polygon geometry.
*
* Returns:
* {DOMElement} A GML polygon node.
*/
polygon: function(geometry) {
var gml = this.createElementNS(this.gmlns, "gml:Polygon");
var rings = geometry.components;
var ringMember, ringGeom, type;
for(var i=0; i<rings.length; ++i) {
type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
ringMember = this.createElementNS(this.gmlns,
"gml:" + type);
ringGeom = this.buildGeometry.linearring.apply(this,
[rings[i]]);
ringMember.appendChild(ringGeom);
gml.appendChild(ringMember);
}
return gml;
},
/**
* Method: buildGeometry.multipolygon
* Given an OpenLayers multipolygon geometry, create a GML multipolygon.
*
* Parameters:
* geometry - {<OpenLayers.Geometry.MultiPolygon>} A multipolygon
* geometry.
*
* Returns:
* {DOMElement} A GML multipolygon node.
*/
multipolygon: function(geometry) {
var gml = this.createElementNS(this.gmlns, "gml:MultiPolygon");
var polys = geometry.components;
var polyMember, polyGeom;
for(var i=0; i<polys.length; ++i) {
polyMember = this.createElementNS(this.gmlns,
"gml:polygonMember");
polyGeom = this.buildGeometry.polygon.apply(this,
[polys[i]]);
polyMember.appendChild(polyGeom);
gml.appendChild(polyMember);
}
return gml;
},
/**
* Method: buildGeometry.bounds
* Given an OpenLayers bounds, create a GML box.
*
* Parameters:
* bounds - {<OpenLayers.Geometry.Bounds>} A bounds object.
*
* Returns:
* {DOMElement} A GML box node.
*/
bounds: function(bounds) {
var gml = this.createElementNS(this.gmlns, "gml:Box");
gml.appendChild(this.buildCoordinatesNode(bounds));
return gml;
}
},
/**
* Method: buildCoordinates
* builds the coordinates XmlNode
* (code)
* <gml:coordinates decimal="." cs="," ts=" ">...</gml:coordinates>
* (end)
*
* Parameters:
* geometry - {<OpenLayers.Geometry>}
*
* Returns:
* {XmlNode} created xmlNode
*/
buildCoordinatesNode: function(geometry) {
var coordinatesNode = this.createElementNS(this.gmlns,
"gml:coordinates");
coordinatesNode.setAttribute("decimal", ".");
coordinatesNode.setAttribute("cs", ",");
coordinatesNode.setAttribute("ts", " ");
var parts = [];
if(geometry instanceof OpenLayers.Bounds){
parts.push(geometry.left + "," + geometry.bottom);
parts.push(geometry.right + "," + geometry.top);
} else {
var points = (geometry.components) ? geometry.components : [geometry];
for(var i=0; i<points.length; i++) {
parts.push(points[i].x + "," + points[i].y);
}
}
var txtNode = this.createTextNode(parts.join(" "));
coordinatesNode.appendChild(txtNode);
return coordinatesNode;
},
CLASS_NAME: "OpenLayers.Format.GML"
});
/* ======================================================================
OpenLayers/Format/GML/Base.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Format/XML.js
* @requires OpenLayers/Format/GML.js
*/
/**
* Though required in the full build, if the GML format is excluded, we set
* the namespace here.
*/
if(!OpenLayers.Format.GML) {
OpenLayers.Format.GML = {};
}
/**
* Class: OpenLayers.Format.GML.Base
* Superclass for GML parsers.
*
* Inherits from:
* - <OpenLayers.Format.XML>
*/
OpenLayers.Format.GML.Base = OpenLayers.Class(OpenLayers.Format.XML, {
/**
* Property: namespaces
* {Object} Mapping of namespace aliases to namespace URIs.
*/
namespaces: {
gml: "http://www.opengis.net/gml",
xlink: "http://www.w3.org/1999/xlink",
xsi: "http://www.w3.org/2001/XMLSchema-instance",
wfs: "http://www.opengis.net/wfs" // this is a convenience for reading wfs:FeatureCollection
},
/**
* Property: defaultPrefix
*/
defaultPrefix: "gml",
/**
* Property: schemaLocation
* {String} Schema location for a particular minor version.
*/
schemaLocation: null,
/**
* APIProperty: featureType
* {Array(String) or String} The local (without prefix) feature typeName(s).
*/
featureType: null,
/**
* APIProperty: featureNS
* {String} The feature namespace. Must be set in the options at
* construction.
*/
featureNS: null,
/**
* APIProperty: featurePrefix
* {String} Namespace alias (or prefix) for feature nodes. Default is
* "feature".
*/
featurePrefix: "feature",
/**
* APIProperty: geometry
* {String} Name of geometry element. Defaults to "geometry". If null, it
* will be set on <read> when the first geometry is parsed.
*/
geometryName: "geometry",
/**
* APIProperty: extractAttributes
* {Boolean} Extract attributes from GML. Default is true.
*/
extractAttributes: true,
/**
* APIProperty: srsName
* {String} URI for spatial reference system. This is optional for
* single part geometries and mandatory for collections and multis.
* If set, the srsName attribute will be written for all geometries.
* Default is null.
*/
srsName: null,
/**
* APIProperty: xy
* {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
* Changing is not recommended, a new Format should be instantiated.
*/
xy: true,
/**
* Property: geometryTypes
* {Object} Maps OpenLayers geometry class names to GML element names.
* Use <setGeometryTypes> before accessing this property.
*/
geometryTypes: null,
/**
* Property: singleFeatureType
* {Boolean} True if there is only 1 featureType, and not an array
* of featuretypes.
*/
singleFeatureType: null,
/**
* Property: autoConfig
* {Boolean} Indicates if the format was configured without a <featureNS>,
* but auto-configured <featureNS> and <featureType> during read.
* Subclasses making use of <featureType> auto-configuration should make
* the first call to the <readNode> method (usually in the read method)
* with true as 3rd argument, so the auto-configured featureType can be
* reset and the format can be reused for subsequent reads with data from
* different featureTypes. Set to false after read if you want to keep the
* auto-configured values.
*/
/**
* Property: regExes
* Compiled regular expressions for manipulating strings.
*/
regExes: {
trimSpace: (/^\s*|\s*$/g),
removeSpace: (/\s*/g),
splitSpace: (/\s+/),
trimComma: (/\s*,\s*/g),
featureMember: (/^(.*:)?featureMembers?$/)
},
/**
* Constructor: OpenLayers.Format.GML.Base
* Instances of this class are not created directly. Use the
* <OpenLayers.Format.GML.v2> or <OpenLayers.Format.GML.v3> constructor
* instead.
*
* Parameters:
* options - {Object} An optional object whose properties will be set on
* this instance.
*
* Valid options properties:
* featureType - {Array(String) or String} Local (without prefix) feature
* typeName(s) (required for write).
* featureNS - {String} Feature namespace (required for write).
* geometryName - {String} Geometry element name (required for write).
*/
initialize: function(options) {
OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
this.setGeometryTypes();
if(options && options.featureNS) {
this.setNamespace(this.featurePrefix, options.featureNS);
}
this.singleFeatureType = !options || (typeof options.featureType === "string");
},
/**
* Method: read
*
* Parameters:
* data - {DOMElement} A gml:featureMember element, a gml:featureMembers
* element, or an element containing either of the above at any level.
*
* Returns:
* {Array(<OpenLayers.Feature.Vector>)} An array of features.
*/
read: function(data) {
if(typeof data == "string") {
data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
}
if(data && data.nodeType == 9) {
data = data.documentElement;
}
var features = [];
this.readNode(data, {features: features}, true);
if(features.length == 0) {
// look for gml:featureMember elements
var elements = this.getElementsByTagNameNS(
data, this.namespaces.gml, "featureMember"
);
if(elements.length) {
for(var i=0, len=elements.length; i<len; ++i) {
this.readNode(elements[i], {features: features}, true);
}
} else {
// look for gml:featureMembers elements (this is v3, but does no harm here)
var elements = this.getElementsByTagNameNS(
data, this.namespaces.gml, "featureMembers"
);
if(elements.length) {
// there can be only one
this.readNode(elements[0], {features: features}, true);
}
}
}
return features;
},
/**
* Method: readNode
* Shorthand for applying one of the named readers given the node
* namespace and local name. Readers take two args (node, obj) and
* generally extend or modify the second.
*
* Parameters:
* node - {DOMElement} The node to be read (required).
* obj - {Object} The object to be modified (optional).
* first - {Boolean} Should be set to true for the first node read. This
* is usually the readNode call in the read method. Without this being
* set, auto-configured properties will stick on subsequent reads.
*
* Returns:
* {Object} The input object, modified (or a new one if none was provided).
*/
readNode: function(node, obj, first) {
// on subsequent calls of format.read(), we want to reset auto-
// configured properties and auto-configure again.
if (first === true && this.autoConfig === true) {
this.featureType = null;
delete this.namespaceAlias[this.featureNS];
delete this.namespaces["feature"];
this.featureNS = null;
}
// featureType auto-configuration
if (!this.featureNS && (!(node.prefix in this.namespaces) &&
node.parentNode.namespaceURI == this.namespaces["gml"] &&
this.regExes.featureMember.test(node.parentNode.nodeName))) {
this.featureType = node.nodeName.split(":").pop();
this.setNamespace("feature", node.namespaceURI);
this.featureNS = node.namespaceURI;
this.autoConfig = true;
}
return OpenLayers.Format.XML.prototype.readNode.apply(this, [node, obj]);
},
/**
* Property: readers
* Contains public functions, grouped by namespace prefix, that will
* be applied when a namespaced node is found matching the function
* name. The function will be applied in the scope of this parser
* with two arguments: the node being read and a context object passed
* from the parent.
*/
readers: {
"gml": {
"_inherit": function(node, obj, container) {
// To be implemented by version specific parsers
},
"featureMember": function(node, obj) {
this.readChildNodes(node, obj);
},
"featureMembers": function(node, obj) {
this.readChildNodes(node, obj);
},
"name": function(node, obj) {
obj.name = this.getChildValue(node);
},
"boundedBy": function(node, obj) {
var container = {};
this.readChildNodes(node, container);
if(container.components && container.components.length > 0) {
obj.bounds = container.components[0];
}
},
"Point": function(node, container) {
var obj = {points: []};
this.readChildNodes(node, obj);
if(!container.components) {
container.components = [];
}
container.components.push(obj.points[0]);
},
"coordinates": function(node, obj) {
var str = this.getChildValue(node).replace(
this.regExes.trimSpace, ""
);
str = str.replace(this.regExes.trimComma, ",");
var pointList = str.split(this.regExes.splitSpace);
var coords;
var numPoints = pointList.length;
var points = new Array(numPoints);
for(var i=0; i<numPoints; ++i) {
coords = pointList[i].split(",");
if (this.xy) {
points[i] = new OpenLayers.Geometry.Point(
coords[0], coords[1], coords[2]
);
} else {
points[i] = new OpenLayers.Geometry.Point(
coords[1], coords[0], coords[2]
);
}
}
obj.points = points;
},
"coord": function(node, obj) {
var coord = {};
this.readChildNodes(node, coord);
if(!obj.points) {
obj.points = [];
}
obj.points.push(new OpenLayers.Geometry.Point(
coord.x, coord.y, coord.z
));
},
"X": function(node, coord) {
coord.x = this.getChildValue(node);
},
"Y": function(node, coord) {
coord.y = this.getChildValue(node);
},
"Z": function(node, coord) {
coord.z = this.getChildValue(node);
},
"MultiPoint": function(node, container) {
var obj = {components: []};
this.readers.gml._inherit.apply(this, [node, obj, container]);
this.readChildNodes(node, obj);
container.components = [
new OpenLayers.Geometry.MultiPoint(obj.components)
];
},
"pointMember": function(node, obj) {
this.readChildNodes(node, obj);
},
"LineString": function(node, container) {
var obj = {};
this.readers.gml._inherit.apply(this, [node, obj, container]);
this.readChildNodes(node, obj);
if(!container.components) {
container.components = [];
}
container.components.push(
new OpenLayers.Geometry.LineString(obj.points)
);
},
"MultiLineString": function(node, container) {
var obj = {components: []};
this.readers.gml._inherit.apply(this, [node, obj, container]);
this.readChildNodes(node, obj);
container.components = [
new OpenLayers.Geometry.MultiLineString(obj.components)
];
},
"lineStringMember": function(node, obj) {
this.readChildNodes(node, obj);
},
"Polygon": function(node, container) {
var obj = {outer: null, inner: []};
this.readers.gml._inherit.apply(this, [node, obj, container]);
this.readChildNodes(node, obj);
obj.inner.unshift(obj.outer);
if(!container.components) {
container.components = [];
}
container.components.push(
new OpenLayers.Geometry.Polygon(obj.inner)
);
},
"LinearRing": function(node, obj) {
var container = {};
this.readers.gml._inherit.apply(this, [node, container]);
this.readChildNodes(node, container);
obj.components = [new OpenLayers.Geometry.LinearRing(
container.points
)];
},
"MultiPolygon": function(node, container) {
var obj = {components: []};
this.readers.gml._inherit.apply(this, [node, obj, container]);
this.readChildNodes(node, obj);
container.components = [
new OpenLayers.Geometry.MultiPolygon(obj.components)
];
},
"polygonMember": function(node, obj) {
this.readChildNodes(node, obj);
},
"GeometryCollection": function(node, container) {
var obj = {components: []};
this.readers.gml._inherit.apply(this, [node, obj, container]);
this.readChildNodes(node, obj);
container.components = [
new OpenLayers.Geometry.Collection(obj.components)
];
},
"geometryMember": function(node, obj) {
this.readChildNodes(node, obj);
}
},
"feature": {
"*": function(node, obj) {
// The node can either be named like the featureType, or it
// can be a child of the feature:featureType. Children can be
// geometry or attributes.
var name;
var local = node.localName || node.nodeName.split(":").pop();
// Since an attribute can have the same name as the feature type
// we only want to read the node as a feature if the parent
// node can have feature nodes as children. In this case, the
// obj.features property is set.
if (obj.features) {
if (!this.singleFeatureType &&
(OpenLayers.Util.indexOf(this.featureType, local) !== -1)) {
name = "_typeName";
} else if(local === this.featureType) {
name = "_typeName";
}
} else {
// Assume attribute elements have one child node and that the child
// is a text node. Otherwise assume it is a geometry node.
if(node.childNodes.length == 0 ||
(node.childNodes.length == 1 && node.firstChild.nodeType == 3)) {
if(this.extractAttributes) {
name = "_attribute";
}
} else {
name = "_geometry";
}
}
if(name) {
this.readers.feature[name].apply(this, [node, obj]);
}
},
"_typeName": function(node, obj) {
var container = {components: [], attributes: {}};
this.readChildNodes(node, container);
// look for common gml namespaced elements
if(container.name) {
container.attributes.name = container.name;
}
var feature = new OpenLayers.Feature.Vector(
container.components[0], container.attributes
);
if (!this.singleFeatureType) {
feature.type = node.nodeName.split(":").pop();
feature.namespace = node.namespaceURI;
}
var fid = node.getAttribute("fid") ||
this.getAttributeNS(node, this.namespaces["gml"], "id");
if(fid) {
feature.fid = fid;
}
if(this.internalProjection && this.externalProjection &&
feature.geometry) {
feature.geometry.transform(
this.externalProjection, this.internalProjection
);
}
if(container.bounds) {
feature.bounds = container.bounds;
}
obj.features.push(feature);
},
"_geometry": function(node, obj) {
if (!this.geometryName) {
this.geometryName = node.nodeName.split(":").pop();
}
this.readChildNodes(node, obj);
},
"_attribute": function(node, obj) {
var local = node.localName || node.nodeName.split(":").pop();
var value = this.getChildValue(node);
obj.attributes[local] = value;
}
},
"wfs": {
"FeatureCollection": function(node, obj) {
this.readChildNodes(node, obj);
}
}
},
/**
* Method: write
*
* Parameters:
* features - {Array(<OpenLayers.Feature.Vector>) | OpenLayers.Feature.Vector}
* An array of features or a single feature.
*
* Returns:
* {String} Given an array of features, a doc with a gml:featureMembers
* element will be returned. Given a single feature, a doc with a
* gml:featureMember element will be returned.
*/
write: function(features) {
var name;
if(OpenLayers.Util.isArray(features)) {
name = "featureMembers";
} else {
name = "featureMember";
}
var root = this.writeNode("gml:" + name, features);
this.setAttributeNS(
root, this.namespaces["xsi"],
"xsi:schemaLocation", this.schemaLocation
);
return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
},
/**
* Property: writers
* As a compliment to the readers property, this structure contains public
* writing functions grouped by namespace alias and named like the
* node names they produce.
*/
writers: {
"gml": {
"featureMember": function(feature) {
var node = this.createElementNSPlus("gml:featureMember");
this.writeNode("feature:_typeName", feature, node);
return node;
},
"MultiPoint": function(geometry) {
var node = this.createElementNSPlus("gml:MultiPoint");
var components = geometry.components || [geometry];
for(var i=0, ii=components.length; i<ii; ++i) {
this.writeNode("pointMember", components[i], node);
}
return node;
},
"pointMember": function(geometry) {
var node = this.createElementNSPlus("gml:pointMember");
this.writeNode("Point", geometry, node);
return node;
},
"MultiLineString": function(geometry) {
var node = this.createElementNSPlus("gml:MultiLineString");
var components = geometry.components || [geometry];
for(var i=0, ii=components.length; i<ii; ++i) {
this.writeNode("lineStringMember", components[i], node);
}
return node;
},
"lineStringMember": function(geometry) {
var node = this.createElementNSPlus("gml:lineStringMember");
this.writeNode("LineString", geometry, node);
return node;
},
"MultiPolygon": function(geometry) {
var node = this.createElementNSPlus("gml:MultiPolygon");
var components = geometry.components || [geometry];
for(var i=0, ii=components.length; i<ii; ++i) {
this.writeNode(
"polygonMember", components[i], node
);
}
return node;
},
"polygonMember": function(geometry) {
var node = this.createElementNSPlus("gml:polygonMember");
this.writeNode("Polygon", geometry, node);
return node;
},
"GeometryCollection": function(geometry) {
var node = this.createElementNSPlus("gml:GeometryCollection");
for(var i=0, len=geometry.components.length; i<len; ++i) {
this.writeNode("geometryMember", geometry.components[i], node);
}
return node;
},
"geometryMember": function(geometry) {
var node = this.createElementNSPlus("gml:geometryMember");
var child = this.writeNode("feature:_geometry", geometry);
node.appendChild(child.firstChild);
return node;
}
},
"feature": {
"_typeName": function(feature) {
var node = this.createElementNSPlus(this.featurePrefix + ":" + this.featureType, {
attributes: {fid: feature.fid}
});
if(feature.geometry) {
this.writeNode("feature:_geometry", feature.geometry, node);
}
for(var name in feature.attributes) {
var value = feature.attributes[name];
if(value != null) {
this.writeNode(
"feature:_attribute",
{name: name, value: value}, node
);
}
}
return node;
},
"_geometry": function(geometry) {
if(this.externalProjection && this.internalProjection) {
geometry = geometry.clone().transform(
this.internalProjection, this.externalProjection
);
}
var node = this.createElementNSPlus(
this.featurePrefix + ":" + this.geometryName
);
var type = this.geometryTypes[geometry.CLASS_NAME];
var child = this.writeNode("gml:" + type, geometry, node);
if(this.srsName) {
child.setAttribute("srsName", this.srsName);
}
return node;
},
"_attribute": function(obj) {
return this.createElementNSPlus(this.featurePrefix + ":" + obj.name, {
value: obj.value
});
}
},
"wfs": {
"FeatureCollection": function(features) {
/**
* This is only here because GML2 only describes abstract
* feature collections. Typically, you would not be using
* the GML format to write wfs elements. This just provides
* some way to write out lists of features. GML3 defines the
* featureMembers element, so that is used by default instead.
*/
var node = this.createElementNSPlus("wfs:FeatureCollection");
for(var i=0, len=features.length; i<len; ++i) {
this.writeNode("gml:featureMember", features[i], node);
}
return node;
}
}
},
/**
* Method: setGeometryTypes
* Sets the <geometryTypes> mapping.
*/
setGeometryTypes: function() {
this.geometryTypes = {
"OpenLayers.Geometry.Point": "Point",
"OpenLayers.Geometry.MultiPoint": "MultiPoint",
"OpenLayers.Geometry.LineString": "LineString",
"OpenLayers.Geometry.MultiLineString": "MultiLineString",
"OpenLayers.Geometry.Polygon": "Polygon",
"OpenLayers.Geometry.MultiPolygon": "MultiPolygon",
"OpenLayers.Geometry.Collection": "GeometryCollection"
};
},
CLASS_NAME: "OpenLayers.Format.GML.Base"
});
/* ======================================================================
OpenLayers/Format/GML/v2.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Format/GML/Base.js
*/
/**
* Class: OpenLayers.Format.GML.v2
* Parses GML version 2.
*
* Inherits from:
* - <OpenLayers.Format.GML.Base>
*/
OpenLayers.Format.GML.v2 = OpenLayers.Class(OpenLayers.Format.GML.Base, {
/**
* Property: schemaLocation
* {String} Schema location for a particular minor version.
*/
schemaLocation: "http://www.opengis.net/gml http://schemas.opengis.net/gml/2.1.2/feature.xsd",
/**
* Constructor: OpenLayers.Format.GML.v2
* Create a parser for GML v2.
*
* Parameters:
* options - {Object} An optional object whose properties will be set on
* this instance.
*
* Valid options properties:
* featureType - {String} Local (without prefix) feature typeName (required).
* featureNS - {String} Feature namespace (required).
* geometryName - {String} Geometry element name.
*/
initialize: function(options) {
OpenLayers.Format.GML.Base.prototype.initialize.apply(this, [options]);
},
/**
* Property: readers
* Contains public functions, grouped by namespace prefix, that will
* be applied when a namespaced node is found matching the function
* name. The function will be applied in the scope of this parser
* with two arguments: the node being read and a context object passed
* from the parent.
*/
readers: {
"gml": OpenLayers.Util.applyDefaults({
"outerBoundaryIs": function(node, container) {
var obj = {};
this.readChildNodes(node, obj);
container.outer = obj.components[0];
},
"innerBoundaryIs": function(node, container) {
var obj = {};
this.readChildNodes(node, obj);
container.inner.push(obj.components[0]);
},
"Box": function(node, container) {
var obj = {};
this.readChildNodes(node, obj);
if(!container.components) {
container.components = [];
}
var min = obj.points[0];
var max = obj.points[1];
container.components.push(
new OpenLayers.Bounds(min.x, min.y, max.x, max.y)
);
}
}, OpenLayers.Format.GML.Base.prototype.readers["gml"]),
"feature": OpenLayers.Format.GML.Base.prototype.readers["feature"],
"wfs": OpenLayers.Format.GML.Base.prototype.readers["wfs"]
},
/**
* Method: write
*
* Parameters:
* features - {Array(<OpenLayers.Feature.Vector>) | OpenLayers.Feature.Vector}
* An array of features or a single feature.
*
* Returns:
* {String} Given an array of features, a doc with a gml:featureMembers
* element will be returned. Given a single feature, a doc with a
* gml:featureMember element will be returned.
*/
write: function(features) {
var name;
if(OpenLayers.Util.isArray(features)) {
// GML2 only has abstract feature collections
// wfs provides a feature collection from a well-known schema
name = "wfs:FeatureCollection";
} else {
name = "gml:featureMember";
}
var root = this.writeNode(name, features);
this.setAttributeNS(
root, this.namespaces["xsi"],
"xsi:schemaLocation", this.schemaLocation
);
return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
},
/**
* Property: writers
* As a compliment to the readers property, this structure contains public
* writing functions grouped by namespace alias and named like the
* node names they produce.
*/
writers: {
"gml": OpenLayers.Util.applyDefaults({
"Point": function(geometry) {
var node = this.createElementNSPlus("gml:Point");
this.writeNode("coordinates", [geometry], node);
return node;
},
"coordinates": function(points) {
var numPoints = points.length;
var parts = new Array(numPoints);
var point;
for(var i=0; i<numPoints; ++i) {
point = points[i];
if(this.xy) {
parts[i] = point.x + "," + point.y;
} else {
parts[i] = point.y + "," + point.x;
}
if(point.z != undefined) { // allow null or undefined
parts[i] += "," + point.z;
}
}
return this.createElementNSPlus("gml:coordinates", {
attributes: {
decimal: ".", cs: ",", ts: " "
},
value: (numPoints == 1) ? parts[0] : parts.join(" ")
});
},
"LineString": function(geometry) {
var node = this.createElementNSPlus("gml:LineString");
this.writeNode("coordinates", geometry.components, node);
return node;
},
"Polygon": function(geometry) {
var node = this.createElementNSPlus("gml:Polygon");
this.writeNode("outerBoundaryIs", geometry.components[0], node);
for(var i=1; i<geometry.components.length; ++i) {
this.writeNode(
"innerBoundaryIs", geometry.components[i], node
);
}
return node;
},
"outerBoundaryIs": function(ring) {
var node = this.createElementNSPlus("gml:outerBoundaryIs");
this.writeNode("LinearRing", ring, node);
return node;
},
"innerBoundaryIs": function(ring) {
var node = this.createElementNSPlus("gml:innerBoundaryIs");
this.writeNode("LinearRing", ring, node);
return node;
},
"LinearRing": function(ring) {
var node = this.createElementNSPlus("gml:LinearRing");
this.writeNode("coordinates", ring.components, node);
return node;
},
"Box": function(bounds) {
var node = this.createElementNSPlus("gml:Box");
this.writeNode("coordinates", [
{x: bounds.left, y: bounds.bottom},
{x: bounds.right, y: bounds.top}
], node);
// srsName attribute is optional for gml:Box
if(this.srsName) {
node.setAttribute("srsName", this.srsName);
}
return node;
}
}, OpenLayers.Format.GML.Base.prototype.writers["gml"]),
"feature": OpenLayers.Format.GML.Base.prototype.writers["feature"],
"wfs": OpenLayers.Format.GML.Base.prototype.writers["wfs"]
},
CLASS_NAME: "OpenLayers.Format.GML.v2"
});
/* ======================================================================
OpenLayers/Format/OGCExceptionReport.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Format/XML.js
*/
/**
* Class: OpenLayers.Format.OGCExceptionReport
* Class to read exception reports for various OGC services and versions.
*
* Inherits from:
* - <OpenLayers.Format.XML>
*/
OpenLayers.Format.OGCExceptionReport = OpenLayers.Class(OpenLayers.Format.XML, {
/**
* Property: namespaces
* {Object} Mapping of namespace aliases to namespace URIs.
*/
namespaces: {
ogc: "http://www.opengis.net/ogc"
},
/**
* Property: regExes
* Compiled regular expressions for manipulating strings.
*/
regExes: {
trimSpace: (/^\s*|\s*$/g),
removeSpace: (/\s*/g),
splitSpace: (/\s+/),
trimComma: (/\s*,\s*/g)
},
/**
* Property: defaultPrefix
*/
defaultPrefix: "ogc",
/**
* Constructor: OpenLayers.Format.OGCExceptionReport
* Create a new parser for OGC exception reports.
*
* Parameters:
* options - {Object} An optional object whose properties will be set on
* this instance.
*/
/**
* APIMethod: read
* Read OGC exception report data from a string, and return an object with
* information about the exceptions.
*
* Parameters:
* data - {String} or {DOMElement} data to read/parse.
*
* Returns:
* {Object} Information about the exceptions that occurred.
*/
read: function(data) {
var result;
if(typeof data == "string") {
data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
}
var root = data.documentElement;
var exceptionInfo = {exceptionReport: null};
if (root) {
this.readChildNodes(data, exceptionInfo);
if (exceptionInfo.exceptionReport === null) {
// fall-back to OWSCommon since this is a common output format for exceptions
// we cannot easily use the ows readers directly since they differ for 1.0 and 1.1
exceptionInfo = new OpenLayers.Format.OWSCommon().read(data);
}
}
return exceptionInfo;
},
/**
* Property: readers
* Contains public functions, grouped by namespace prefix, that will
* be applied when a namespaced node is found matching the function
* name. The function will be applied in the scope of this parser
* with two arguments: the node being read and a context object passed
* from the parent.
*/
readers: {
"ogc": {
"ServiceExceptionReport": function(node, obj) {
obj.exceptionReport = {exceptions: []};
this.readChildNodes(node, obj.exceptionReport);
},
"ServiceException": function(node, exceptionReport) {
var exception = {
code: node.getAttribute("code"),
locator: node.getAttribute("locator"),
text: this.getChildValue(node)
};
exceptionReport.exceptions.push(exception);
}
}
},
CLASS_NAME: "OpenLayers.Format.OGCExceptionReport"
});
/* ======================================================================
OpenLayers/Format/XML/VersionedOGC.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Format/XML.js
* @requires OpenLayers/Format/OGCExceptionReport.js
*/
/**
* Class: OpenLayers.Format.XML.VersionedOGC
* Base class for versioned formats, i.e. a format which supports multiple
* versions.
*
* To enable checking if parsing succeeded, you will need to define a property
* called errorProperty on the parser you want to check. The parser will then
* check the returned object to see if that property is present. If it is, it
* assumes the parsing was successful. If it is not present (or is null), it will
* pass the document through an OGCExceptionReport parser.
*
* If errorProperty is undefined for the parser, this error checking mechanism
* will be disabled.
*
*
*
* Inherits from:
* - <OpenLayers.Format.XML>
*/
OpenLayers.Format.XML.VersionedOGC = OpenLayers.Class(OpenLayers.Format.XML, {
/**
* APIProperty: defaultVersion
* {String} Version number to assume if none found.
*/
defaultVersion: null,
/**
* APIProperty: version
* {String} Specify a version string if one is known.
*/
version: null,
/**
* APIProperty: profile
* {String} If provided, use a custom profile.
*/
profile: null,
/**
* APIProperty: allowFallback
* {Boolean} If a profiled parser cannot be found for the returned version,
* use a non-profiled parser as the fallback. Application code using this
* should take into account that the return object structure might be
* missing the specifics of the profile. Defaults to false.
*/
allowFallback: false,
/**
* Property: name
* {String} The name of this parser, this is the part of the CLASS_NAME
* except for "OpenLayers.Format."
*/
name: null,
/**
* APIProperty: stringifyOutput
* {Boolean} If true, write will return a string otherwise a DOMElement.
* Default is false.
*/
stringifyOutput: false,
/**
* Property: parser
* {Object} Instance of the versioned parser. Cached for multiple read and
* write calls of the same version.
*/
parser: null,
/**
* Constructor: OpenLayers.Format.XML.VersionedOGC.
* Constructor.
*
* Parameters:
* options - {Object} Optional object whose properties will be set on
* the object.
*/
initialize: function(options) {
OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
var className = this.CLASS_NAME;
this.name = className.substring(className.lastIndexOf(".")+1);
},
/**
* Method: getVersion
* Returns the version to use. Subclasses can override this function
* if a different version detection is needed.
*
* Parameters:
* root - {DOMElement}
* options - {Object} Optional configuration object.
*
* Returns:
* {String} The version to use.
*/
getVersion: function(root, options) {
var version;
// read
if (root) {
version = this.version;
if(!version) {
version = root.getAttribute("version");
if(!version) {
version = this.defaultVersion;
}
}
} else { // write
version = (options && options.version) ||
this.version || this.defaultVersion;
}
return version;
},
/**
* Method: getParser
* Get an instance of the cached parser if available, otherwise create one.
*
* Parameters:
* version - {String}
*
* Returns:
* {<OpenLayers.Format>}
*/
getParser: function(version) {
version = version || this.defaultVersion;
var profile = this.profile ? "_" + this.profile : "";
if(!this.parser || this.parser.VERSION != version) {
var format = OpenLayers.Format[this.name][
"v" + version.replace(/\./g, "_") + profile
];
if(!format) {
if (profile !== "" && this.allowFallback) {
// fallback to the non-profiled version of the parser
profile = "";
format = OpenLayers.Format[this.name][
"v" + version.replace(/\./g, "_")
];
}
if (!format) {
throw "Can't find a " + this.name + " parser for version " +
version + profile;
}
}
this.parser = new format(this.options);
}
return this.parser;
},
/**
* APIMethod: write
* Write a document.
*
* Parameters:
* obj - {Object} An object representing the document.
* options - {Object} Optional configuration object.
*
* Returns:
* {String} The document as a string
*/
write: function(obj, options) {
var version = this.getVersion(null, options);
this.parser = this.getParser(version);
var root = this.parser.write(obj, options);
if (this.stringifyOutput === false) {
return root;
} else {
return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
}
},
/**
* APIMethod: read
* Read a doc and return an object representing the document.
*
* Parameters:
* data - {String | DOMElement} Data to read.
* options - {Object} Options for the reader.
*
* Returns:
* {Object} An object representing the document.
*/
read: function(data, options) {
if(typeof data == "string") {
data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
}
var root = data.documentElement;
var version = this.getVersion(root);
this.parser = this.getParser(version); // Select the parser
var obj = this.parser.read(data, options); // Parse the data
var errorProperty = this.parser.errorProperty || null;
if (errorProperty !== null && obj[errorProperty] === undefined) {
// an error must have happened, so parse it and report back
var format = new OpenLayers.Format.OGCExceptionReport();
obj.error = format.read(data);
}
obj.version = version;
obj.requestType = this.name;
return obj;
},
CLASS_NAME: "OpenLayers.Format.XML.VersionedOGC"
});
/* ======================================================================
OpenLayers/Filter/Logical.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Filter.js
*/
/**
* Class: OpenLayers.Filter.Logical
* This class represents ogc:And, ogc:Or and ogc:Not rules.
*
* Inherits from:
* - <OpenLayers.Filter>
*/
OpenLayers.Filter.Logical = OpenLayers.Class(OpenLayers.Filter, {
/**
* APIProperty: filters
* {Array(<OpenLayers.Filter>)} Child filters for this filter.
*/
filters: null,
/**
* APIProperty: type
* {String} type of logical operator. Available types are:
* - OpenLayers.Filter.Logical.AND = "&&";
* - OpenLayers.Filter.Logical.OR = "||";
* - OpenLayers.Filter.Logical.NOT = "!";
*/
type: null,
/**
* Constructor: OpenLayers.Filter.Logical
* Creates a logical filter (And, Or, Not).
*
* Parameters:
* options - {Object} An optional object with properties to set on the
* filter.
*
* Returns:
* {<OpenLayers.Filter.Logical>}
*/
initialize: function(options) {
this.filters = [];
OpenLayers.Filter.prototype.initialize.apply(this, [options]);
},
/**
* APIMethod: destroy
* Remove reference to child filters.
*/
destroy: function() {
this.filters = null;
OpenLayers.Filter.prototype.destroy.apply(this);
},
/**
* APIMethod: evaluate
* Evaluates this filter in a specific context.
*
* Parameters:
* context - {Object} Context to use in evaluating the filter. A vector
* feature may also be provided to evaluate feature attributes in
* comparison filters or geometries in spatial filters.
*
* Returns:
* {Boolean} The filter applies.
*/
evaluate: function(context) {
var i, len;
switch(this.type) {
case OpenLayers.Filter.Logical.AND:
for (i=0, len=this.filters.length; i<len; i++) {
if (this.filters[i].evaluate(context) == false) {
return false;
}
}
return true;
case OpenLayers.Filter.Logical.OR:
for (i=0, len=this.filters.length; i<len; i++) {
if (this.filters[i].evaluate(context) == true) {
return true;
}
}
return false;
case OpenLayers.Filter.Logical.NOT:
return (!this.filters[0].evaluate(context));
}
return undefined;
},
/**
* APIMethod: clone
* Clones this filter.
*
* Returns:
* {<OpenLayers.Filter.Logical>} Clone of this filter.
*/
clone: function() {
var filters = [];
for(var i=0, len=this.filters.length; i<len; ++i) {
filters.push(this.filters[i].clone());
}
return new OpenLayers.Filter.Logical({
type: this.type,
filters: filters
});
},
CLASS_NAME: "OpenLayers.Filter.Logical"
});
OpenLayers.Filter.Logical.AND = "&&";
OpenLayers.Filter.Logical.OR = "||";
OpenLayers.Filter.Logical.NOT = "!";
/* ======================================================================
OpenLayers/Filter/Comparison.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Filter.js
*/
/**
* Class: OpenLayers.Filter.Comparison
* This class represents a comparison filter.
*
* Inherits from:
* - <OpenLayers.Filter>
*/
OpenLayers.Filter.Comparison = OpenLayers.Class(OpenLayers.Filter, {
/**
* APIProperty: type
* {String} type: type of the comparison. This is one of
* - OpenLayers.Filter.Comparison.EQUAL_TO = "==";
* - OpenLayers.Filter.Comparison.NOT_EQUAL_TO = "!=";
* - OpenLayers.Filter.Comparison.LESS_THAN = "<";
* - OpenLayers.Filter.Comparison.GREATER_THAN = ">";
* - OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO = "<=";
* - OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO = ">=";
* - OpenLayers.Filter.Comparison.BETWEEN = "..";
* - OpenLayers.Filter.Comparison.LIKE = "~";
* - OpenLayers.Filter.Comparison.IS_NULL = "NULL";
*/
type: null,
/**
* APIProperty: property
* {String}
* name of the context property to compare
*/
property: null,
/**
* APIProperty: value
* {Number} or {String}
* comparison value for binary comparisons. In the case of a String, this
* can be a combination of text and propertyNames in the form
* "literal ${propertyName}"
*/
value: null,
/**
* Property: matchCase
* {Boolean} Force case sensitive searches for EQUAL_TO and NOT_EQUAL_TO
* comparisons. The Filter Encoding 1.1 specification added a matchCase
* attribute to ogc:PropertyIsEqualTo and ogc:PropertyIsNotEqualTo
* elements. This property will be serialized with those elements only
* if using the v1.1.0 filter format. However, when evaluating filters
* here, the matchCase property will always be respected (for EQUAL_TO
* and NOT_EQUAL_TO). Default is true.
*/
matchCase: true,
/**
* APIProperty: lowerBoundary
* {Number} or {String}
* lower boundary for between comparisons. In the case of a String, this
* can be a combination of text and propertyNames in the form
* "literal ${propertyName}"
*/
lowerBoundary: null,
/**
* APIProperty: upperBoundary
* {Number} or {String}
* upper boundary for between comparisons. In the case of a String, this
* can be a combination of text and propertyNames in the form
* "literal ${propertyName}"
*/
upperBoundary: null,
/**
* Constructor: OpenLayers.Filter.Comparison
* Creates a comparison rule.
*
* Parameters:
* options - {Object} An optional object with properties to set on the
* rule
*
* Returns:
* {<OpenLayers.Filter.Comparison>}
*/
initialize: function(options) {
OpenLayers.Filter.prototype.initialize.apply(this, [options]);
// since matchCase on PropertyIsLike is not schema compliant, we only
// want to use this if explicitly asked for
if (this.type === OpenLayers.Filter.Comparison.LIKE
&& options.matchCase === undefined) {
this.matchCase = null;
}
},
/**
* APIMethod: evaluate
* Evaluates this filter in a specific context.
*
* Parameters:
* context - {Object} Context to use in evaluating the filter. If a vector
* feature is provided, the feature.attributes will be used as context.
*
* Returns:
* {Boolean} The filter applies.
*/
evaluate: function(context) {
if (context instanceof OpenLayers.Feature.Vector) {
context = context.attributes;
}
var result = false;
var got = context[this.property];
if (got === undefined) {
return false;
}
var exp;
switch(this.type) {
case OpenLayers.Filter.Comparison.EQUAL_TO:
exp = this.value;
if(!this.matchCase &&
typeof got == "string" && typeof exp == "string") {
result = (got.toUpperCase() == exp.toUpperCase());
} else {
result = (got == exp);
}
break;
case OpenLayers.Filter.Comparison.NOT_EQUAL_TO:
exp = this.value;
if(!this.matchCase &&
typeof got == "string" && typeof exp == "string") {
result = (got.toUpperCase() != exp.toUpperCase());
} else {
result = (got != exp);
}
break;
case OpenLayers.Filter.Comparison.LESS_THAN:
result = got < this.value;
break;
case OpenLayers.Filter.Comparison.GREATER_THAN:
result = got > this.value;
break;
case OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO:
result = got <= this.value;
break;
case OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO:
result = got >= this.value;
break;
case OpenLayers.Filter.Comparison.BETWEEN:
result = (got >= this.lowerBoundary) &&
(got <= this.upperBoundary);
break;
case OpenLayers.Filter.Comparison.LIKE:
var regexp = new RegExp(this.value, "gi");
result = regexp.test(got);
break;
case OpenLayers.Filter.Comparison.IS_NULL:
result = (got === null);
break;
}
return result;
},
/**
* APIMethod: value2regex
* Converts the value of this rule into a regular expression string,
* according to the wildcard characters specified. This method has to
* be called after instantiation of this class, if the value is not a
* regular expression already.
*
* Parameters:
* wildCard - {Char} wildcard character in the above value, default
* is "*"
* singleChar - {Char} single-character wildcard in the above value
* default is "."
* escapeChar - {Char} escape character in the above value, default is
* "!"
*
* Returns:
* {String} regular expression string
*/
value2regex: function(wildCard, singleChar, escapeChar) {
if (wildCard == ".") {
throw new Error("'.' is an unsupported wildCard character for " +
"OpenLayers.Filter.Comparison");
}
// set UMN MapServer defaults for unspecified parameters
wildCard = wildCard ? wildCard : "*";
singleChar = singleChar ? singleChar : ".";
escapeChar = escapeChar ? escapeChar : "!";
this.value = this.value.replace(
new RegExp("\\"+escapeChar+"(.|$)", "g"), "\\$1");
this.value = this.value.replace(
new RegExp("\\"+singleChar, "g"), ".");
this.value = this.value.replace(
new RegExp("\\"+wildCard, "g"), ".*");
this.value = this.value.replace(
new RegExp("\\\\.\\*", "g"), "\\"+wildCard);
this.value = this.value.replace(
new RegExp("\\\\\\.", "g"), "\\"+singleChar);
return this.value;
},
/**
* Method: regex2value
* Convert the value of this rule from a regular expression string into an
* ogc literal string using a wildCard of *, a singleChar of ., and an
* escape of !. Leaves the <value> property unmodified.
*
* Returns:
* {String} A string value.
*/
regex2value: function() {
var value = this.value;
// replace ! with !!
value = value.replace(/!/g, "!!");
// replace \. with !. (watching out for \\.)
value = value.replace(/(\\)?\\\./g, function($0, $1) {
return $1 ? $0 : "!.";
});
// replace \* with #* (watching out for \\*)
value = value.replace(/(\\)?\\\*/g, function($0, $1) {
return $1 ? $0 : "!*";
});
// replace \\ with \
value = value.replace(/\\\\/g, "\\");
// convert .* to * (the sequence #.* is not allowed)
value = value.replace(/\.\*/g, "*");
return value;
},
/**
* APIMethod: clone
* Clones this filter.
*
* Returns:
* {<OpenLayers.Filter.Comparison>} Clone of this filter.
*/
clone: function() {
return OpenLayers.Util.extend(new OpenLayers.Filter.Comparison(), this);
},
CLASS_NAME: "OpenLayers.Filter.Comparison"
});
OpenLayers.Filter.Comparison.EQUAL_TO = "==";
OpenLayers.Filter.Comparison.NOT_EQUAL_TO = "!=";
OpenLayers.Filter.Comparison.LESS_THAN = "<";
OpenLayers.Filter.Comparison.GREATER_THAN = ">";
OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO = "<=";
OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO = ">=";
OpenLayers.Filter.Comparison.BETWEEN = "..";
OpenLayers.Filter.Comparison.LIKE = "~";
OpenLayers.Filter.Comparison.IS_NULL = "NULL";
/* ======================================================================
OpenLayers/Format/Filter.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Format/XML/VersionedOGC.js
* @requires OpenLayers/Filter/FeatureId.js
* @requires OpenLayers/Filter/Logical.js
* @requires OpenLayers/Filter/Comparison.js
*/
/**
* Class: OpenLayers.Format.Filter
* Read/Write ogc:Filter. Create a new instance with the <OpenLayers.Format.Filter>
* constructor.
*
* Inherits from:
* - <OpenLayers.Format.XML.VersionedOGC>
*/
OpenLayers.Format.Filter = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
/**
* APIProperty: defaultVersion
* {String} Version number to assume if none found. Default is "1.0.0".
*/
defaultVersion: "1.0.0",
/**
* APIMethod: write
* Write an ogc:Filter given a filter object.
*
* Parameters:
* filter - {<OpenLayers.Filter>} An filter.
* options - {Object} Optional configuration object.
*
* Returns:
* {Elment} An ogc:Filter element node.
*/
/**
* APIMethod: read
* Read and Filter doc and return an object representing the Filter.
*
* Parameters:
* data - {String | DOMElement} Data to read.
*
* Returns:
* {<OpenLayers.Filter>} A filter object.
*/
CLASS_NAME: "OpenLayers.Format.Filter"
});
/* ======================================================================
OpenLayers/Filter/Function.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Filter.js
*/
/**
* Class: OpenLayers.Filter.Function
* This class represents a filter function.
* We are using this class for creation of complex
* filters that can contain filter functions as values.
* Nesting function as other functions parameter is supported.
*
* Inherits from:
* - <OpenLayers.Filter>
*/
OpenLayers.Filter.Function = OpenLayers.Class(OpenLayers.Filter, {
/**
* APIProperty: name
* {String} Name of the function.
*/
name: null,
/**
* APIProperty: params
* {Array(<OpenLayers.Filter.Function> || String || Number)} Function parameters
* For now support only other Functions, String or Number
*/
params: null,
/**
* Constructor: OpenLayers.Filter.Function
* Creates a filter function.
*
* Parameters:
* options - {Object} An optional object with properties to set on the
* function.
*
* Returns:
* {<OpenLayers.Filter.Function>}
*/
CLASS_NAME: "OpenLayers.Filter.Function"
});
/* ======================================================================
OpenLayers/BaseTypes/Date.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/SingleFile.js
*/
/**
* Namespace: OpenLayers.Date
* Contains implementations of Date.parse and date.toISOString that match the
* ECMAScript 5 specification for parsing RFC 3339 dates.
* http://tools.ietf.org/html/rfc3339
*/
OpenLayers.Date = {
/**
* APIProperty: dateRegEx
* The regex to be used for validating dates. You can provide your own
* regex for instance for adding support for years before BC. Default
* value is: /^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:(?:T(\d{1,2}):(\d{2}):(\d{2}(?:\.\d+)?)(Z|(?:[+-]\d{1,2}(?::(\d{2}))?)))|Z)?$/
*/
dateRegEx: /^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:(?:T(\d{1,2}):(\d{2}):(\d{2}(?:\.\d+)?)(Z|(?:[+-]\d{1,2}(?::(\d{2}))?)))|Z)?$/,
/**
* APIMethod: toISOString
* Generates a string representing a date. The format of the string follows
* the profile of ISO 8601 for date and time on the Internet (see
* http://tools.ietf.org/html/rfc3339). If the toISOString method is
* available on the Date prototype, that is used. The toISOString
* method for Date instances is defined in ECMA-262.
*
* Parameters:
* date - {Date} A date object.
*
* Returns:
* {String} A string representing the date (e.g.
* "2010-08-07T16:58:23.123Z"). If the date does not have a valid time
* (i.e. isNaN(date.getTime())) this method returns the string "Invalid
* Date". The ECMA standard says the toISOString method should throw
* RangeError in this case, but Firefox returns a string instead. For
* best results, use isNaN(date.getTime()) to determine date validity
* before generating date strings.
*/
toISOString: (function() {
if ("toISOString" in Date.prototype) {
return function(date) {
return date.toISOString();
};
} else {
return function(date) {
var str;
if (isNaN(date.getTime())) {
// ECMA-262 says throw RangeError, Firefox returns
// "Invalid Date"
str = "Invalid Date";
} else {
str =
date.getUTCFullYear() + "-" +
OpenLayers.Number.zeroPad(date.getUTCMonth() + 1, 2) + "-" +
OpenLayers.Number.zeroPad(date.getUTCDate(), 2) + "T" +
OpenLayers.Number.zeroPad(date.getUTCHours(), 2) + ":" +
OpenLayers.Number.zeroPad(date.getUTCMinutes(), 2) + ":" +
OpenLayers.Number.zeroPad(date.getUTCSeconds(), 2) + "." +
OpenLayers.Number.zeroPad(date.getUTCMilliseconds(), 3) + "Z";
}
return str;
};
}
})(),
/**
* APIMethod: parse
* Generate a date object from a string. The format for the string follows
* the profile of ISO 8601 for date and time on the Internet (see
* http://tools.ietf.org/html/rfc3339). We don't call the native
* Date.parse because of inconsistency between implmentations. In
* Chrome, calling Date.parse with a string that doesn't contain any
* indication of the timezone (e.g. "2011"), the date is interpreted
* in local time. On Firefox, the assumption is UTC.
*
* Parameters:
* str - {String} A string representing the date (e.g.
* "2010", "2010-08", "2010-08-07", "2010-08-07T16:58:23.123Z",
* "2010-08-07T11:58:23.123-06").
*
* Returns:
* {Date} A date object. If the string could not be parsed, an invalid
* date is returned (i.e. isNaN(date.getTime())).
*/
parse: function(str) {
var date;
var match = str.match(this.dateRegEx);
if (match && (match[1] || match[7])) { // must have at least year or time
var year = parseInt(match[1], 10) || 0;
var month = (parseInt(match[2], 10) - 1) || 0;
var day = parseInt(match[3], 10) || 1;
date = new Date(Date.UTC(year, month, day));
// optional time
var type = match[7];
if (type) {
var hours = parseInt(match[4], 10);
var minutes = parseInt(match[5], 10);
var secFrac = parseFloat(match[6]);
var seconds = secFrac | 0;
var milliseconds = Math.round(1000 * (secFrac - seconds));
date.setUTCHours(hours, minutes, seconds, milliseconds);
// check offset
if (type !== "Z") {
var hoursOffset = parseInt(type, 10);
var minutesOffset = parseInt(match[8], 10) || 0;
var offset = -1000 * (60 * (hoursOffset * 60) + minutesOffset * 60);
date = new Date(date.getTime() + offset);
}
}
} else {
date = new Date("invalid");
}
return date;
}
};
/* ======================================================================
OpenLayers/Format/Filter/v1.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Format/Filter.js
* @requires OpenLayers/Format/XML.js
* @requires OpenLayers/Filter/Function.js
* @requires OpenLayers/BaseTypes/Date.js
*/
/**
* Class: OpenLayers.Format.Filter.v1
* Superclass for Filter version 1 parsers.
*
* Inherits from:
* - <OpenLayers.Format.XML>
*/
OpenLayers.Format.Filter.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
/**
* Property: namespaces
* {Object} Mapping of namespace aliases to namespace URIs.
*/
namespaces: {
ogc: "http://www.opengis.net/ogc",
gml: "http://www.opengis.net/gml",
xlink: "http://www.w3.org/1999/xlink",
xsi: "http://www.w3.org/2001/XMLSchema-instance"
},
/**
* Property: defaultPrefix
*/
defaultPrefix: "ogc",
/**
* Property: schemaLocation
* {String} Schema location for a particular minor version.
*/
schemaLocation: null,
/**
* Constructor: OpenLayers.Format.Filter.v1
* Instances of this class are not created directly. Use the
* <OpenLayers.Format.Filter> constructor instead.
*
* Parameters:
* options - {Object} An optional object whose properties will be set on
* this instance.
*/
initialize: function(options) {
OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
},
/**
* Method: read
*
* Parameters:
* data - {DOMElement} A Filter document element.
*
* Returns:
* {<OpenLayers.Filter>} A filter object.
*/
read: function(data) {
var obj = {};
this.readers.ogc["Filter"].apply(this, [data, obj]);
return obj.filter;
},
/**
* Property: readers
* Contains public functions, grouped by namespace prefix, that will
* be applied when a namespaced node is found matching the function
* name. The function will be applied in the scope of this parser
* with two arguments: the node being read and a context object passed
* from the parent.
*/
readers: {
"ogc": {
"_expression": function(node) {
// only the simplest of ogc:expression handled
// "some text and an <PropertyName>attribute</PropertyName>"}
var obj, value = "";
for(var child=node.firstChild; child; child=child.nextSibling) {
switch(child.nodeType) {
case 1:
obj = this.readNode(child);
if (obj.property) {
value += "${" + obj.property + "}";
} else if (obj.value !== undefined) {
value += obj.value;
}
break;
case 3: // text node
case 4: // cdata section
value += child.nodeValue;
}
}
return value;
},
"Filter": function(node, parent) {
// Filters correspond to subclasses of OpenLayers.Filter.
// Since they contain information we don't persist, we
// create a temporary object and then pass on the filter
// (ogc:Filter) to the parent obj.
var obj = {
fids: [],
filters: []
};
this.readChildNodes(node, obj);
if(obj.fids.length > 0) {
parent.filter = new OpenLayers.Filter.FeatureId({
fids: obj.fids
});
} else if(obj.filters.length > 0) {
parent.filter = obj.filters[0];
}
},
"FeatureId": function(node, obj) {
var fid = node.getAttribute("fid");
if(fid) {
obj.fids.push(fid);
}
},
"And": function(node, obj) {
var filter = new OpenLayers.Filter.Logical({
type: OpenLayers.Filter.Logical.AND
});
this.readChildNodes(node, filter);
obj.filters.push(filter);
},
"Or": function(node, obj) {
var filter = new OpenLayers.Filter.Logical({
type: OpenLayers.Filter.Logical.OR
});
this.readChildNodes(node, filter);
obj.filters.push(filter);
},
"Not": function(node, obj) {
var filter = new OpenLayers.Filter.Logical({
type: OpenLayers.Filter.Logical.NOT
});
this.readChildNodes(node, filter);
obj.filters.push(filter);
},
"PropertyIsLessThan": function(node, obj) {
var filter = new OpenLayers.Filter.Comparison({
type: OpenLayers.Filter.Comparison.LESS_THAN
});
this.readChildNodes(node, filter);
obj.filters.push(filter);
},
"PropertyIsGreaterThan": function(node, obj) {
var filter = new OpenLayers.Filter.Comparison({
type: OpenLayers.Filter.Comparison.GREATER_THAN
});
this.readChildNodes(node, filter);
obj.filters.push(filter);
},
"PropertyIsLessThanOrEqualTo": function(node, obj) {
var filter = new OpenLayers.Filter.Comparison({
type: OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO
});
this.readChildNodes(node, filter);
obj.filters.push(filter);
},
"PropertyIsGreaterThanOrEqualTo": function(node, obj) {
var filter = new OpenLayers.Filter.Comparison({
type: OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO
});
this.readChildNodes(node, filter);
obj.filters.push(filter);
},
"PropertyIsBetween": function(node, obj) {
var filter = new OpenLayers.Filter.Comparison({
type: OpenLayers.Filter.Comparison.BETWEEN
});
this.readChildNodes(node, filter);
obj.filters.push(filter);
},
"Literal": function(node, obj) {
obj.value = OpenLayers.String.numericIf(
this.getChildValue(node), true);
},
"PropertyName": function(node, filter) {
filter.property = this.getChildValue(node);
},
"LowerBoundary": function(node, filter) {
filter.lowerBoundary = OpenLayers.String.numericIf(
this.readers.ogc._expression.call(this, node), true);
},
"UpperBoundary": function(node, filter) {
filter.upperBoundary = OpenLayers.String.numericIf(
this.readers.ogc._expression.call(this, node), true);
},
"Intersects": function(node, obj) {
this.readSpatial(node, obj, OpenLayers.Filter.Spatial.INTERSECTS);
},
"Within": function(node, obj) {
this.readSpatial(node, obj, OpenLayers.Filter.Spatial.WITHIN);
},
"Contains": function(node, obj) {
this.readSpatial(node, obj, OpenLayers.Filter.Spatial.CONTAINS);
},
"DWithin": function(node, obj) {
this.readSpatial(node, obj, OpenLayers.Filter.Spatial.DWITHIN);
},
"Distance": function(node, obj) {
obj.distance = parseInt(this.getChildValue(node));
obj.distanceUnits = node.getAttribute("units");
},
"Function": function(node, obj) {
//TODO write decoder for it
return;
},
"PropertyIsNull": function(node, obj) {
var filter = new OpenLayers.Filter.Comparison({
type: OpenLayers.Filter.Comparison.IS_NULL
});
this.readChildNodes(node, filter);
obj.filters.push(filter);
}
}
},
/**
* Method: readSpatial
*
* Read a {<OpenLayers.Filter.Spatial>} filter.
*
* Parameters:
* node - {DOMElement} A DOM element that contains an ogc:expression.
* obj - {Object} The target object.
* type - {String} One of the OpenLayers.Filter.Spatial.* constants.
*
* Returns:
* {<OpenLayers.Filter.Spatial>} The created filter.
*/
readSpatial: function(node, obj, type) {
var filter = new OpenLayers.Filter.Spatial({
type: type
});
this.readChildNodes(node, filter);
filter.value = filter.components[0];
delete filter.components;
obj.filters.push(filter);
},
/**
* APIMethod: encodeLiteral
* Generates the string representation of a value for use in <Literal>
* elements. The default encoder writes Date values as ISO 8601
* strings.
*
* Parameters:
* value - {Object} Literal value to encode
*
* Returns:
* {String} String representation of the provided value.
*/
encodeLiteral: function(value) {
if (value instanceof Date) {
value = OpenLayers.Date.toISOString(value);
}
return value;
},
/**
* Method: writeOgcExpression
* Limited support for writing OGC expressions. Currently it supports
* (<OpenLayers.Filter.Function> || String || Number)
*
* Parameters:
* value - (<OpenLayers.Filter.Function> || String || Number)
* node - {DOMElement} A parent DOM element
*
* Returns:
* {DOMElement} Updated node element.
*/
writeOgcExpression: function(value, node) {
if (value instanceof OpenLayers.Filter.Function){
this.writeNode("Function", value, node);
} else {
this.writeNode("Literal", value, node);
}
return node;
},
/**
* Method: write
*
* Parameters:
* filter - {<OpenLayers.Filter>} A filter object.
*
* Returns:
* {DOMElement} An ogc:Filter element.
*/
write: function(filter) {
return this.writers.ogc["Filter"].apply(this, [filter]);
},
/**
* Property: writers
* As a compliment to the readers property, this structure contains public
* writing functions grouped by namespace alias and named like the
* node names they produce.
*/
writers: {
"ogc": {
"Filter": function(filter) {
var node = this.createElementNSPlus("ogc:Filter");
this.writeNode(this.getFilterType(filter), filter, node);
return node;
},
"_featureIds": function(filter) {
var node = this.createDocumentFragment();
for (var i=0, ii=filter.fids.length; i<ii; ++i) {
this.writeNode("ogc:FeatureId", filter.fids[i], node);
}
return node;
},
"FeatureId": function(fid) {
return this.createElementNSPlus("ogc:FeatureId", {
attributes: {fid: fid}
});
},
"And": function(filter) {
var node = this.createElementNSPlus("ogc:And");
var childFilter;
for (var i=0, ii=filter.filters.length; i<ii; ++i) {
childFilter = filter.filters[i];
this.writeNode(
this.getFilterType(childFilter), childFilter, node
);
}
return node;
},
"Or": function(filter) {
var node = this.createElementNSPlus("ogc:Or");
var childFilter;
for (var i=0, ii=filter.filters.length; i<ii; ++i) {
childFilter = filter.filters[i];
this.writeNode(
this.getFilterType(childFilter), childFilter, node
);
}
return node;
},
"Not": function(filter) {
var node = this.createElementNSPlus("ogc:Not");
var childFilter = filter.filters[0];
this.writeNode(
this.getFilterType(childFilter), childFilter, node
);
return node;
},
"PropertyIsLessThan": function(filter) {
var node = this.createElementNSPlus("ogc:PropertyIsLessThan");
// no ogc:expression handling for PropertyName for now
this.writeNode("PropertyName", filter, node);
// handle Literals or Functions for now
this.writeOgcExpression(filter.value, node);
return node;
},
"PropertyIsGreaterThan": function(filter) {
var node = this.createElementNSPlus("ogc:PropertyIsGreaterThan");
// no ogc:expression handling for PropertyName for now
this.writeNode("PropertyName", filter, node);
// handle Literals or Functions for now
this.writeOgcExpression(filter.value, node);
return node;
},
"PropertyIsLessThanOrEqualTo": function(filter) {
var node = this.createElementNSPlus("ogc:PropertyIsLessThanOrEqualTo");
// no ogc:expression handling for PropertyName for now
this.writeNode("PropertyName", filter, node);
// handle Literals or Functions for now
this.writeOgcExpression(filter.value, node);
return node;
},
"PropertyIsGreaterThanOrEqualTo": function(filter) {
var node = this.createElementNSPlus("ogc:PropertyIsGreaterThanOrEqualTo");
// no ogc:expression handling for PropertyName for now
this.writeNode("PropertyName", filter, node);
// handle Literals or Functions for now
this.writeOgcExpression(filter.value, node);
return node;
},
"PropertyIsBetween": function(filter) {
var node = this.createElementNSPlus("ogc:PropertyIsBetween");
// no ogc:expression handling for PropertyName for now
this.writeNode("PropertyName", filter, node);
this.writeNode("LowerBoundary", filter, node);
this.writeNode("UpperBoundary", filter, node);
return node;
},
"PropertyName": function(filter) {
// no ogc:expression handling for now
return this.createElementNSPlus("ogc:PropertyName", {
value: filter.property
});
},
"Literal": function(value) {
var encode = this.encodeLiteral ||
OpenLayers.Format.Filter.v1.prototype.encodeLiteral;
return this.createElementNSPlus("ogc:Literal", {
value: encode(value)
});
},
"LowerBoundary": function(filter) {
// handle Literals or Functions for now
var node = this.createElementNSPlus("ogc:LowerBoundary");
this.writeOgcExpression(filter.lowerBoundary, node);
return node;
},
"UpperBoundary": function(filter) {
// handle Literals or Functions for now
var node = this.createElementNSPlus("ogc:UpperBoundary");
this.writeNode("Literal", filter.upperBoundary, node);
return node;
},
"INTERSECTS": function(filter) {
return this.writeSpatial(filter, "Intersects");
},
"WITHIN": function(filter) {
return this.writeSpatial(filter, "Within");
},
"CONTAINS": function(filter) {
return this.writeSpatial(filter, "Contains");
},
"DWITHIN": function(filter) {
var node = this.writeSpatial(filter, "DWithin");
this.writeNode("Distance", filter, node);
return node;
},
"Distance": function(filter) {
return this.createElementNSPlus("ogc:Distance", {
attributes: {
units: filter.distanceUnits
},
value: filter.distance
});
},
"Function": function(filter) {
var node = this.createElementNSPlus("ogc:Function", {
attributes: {
name: filter.name
}
});
var params = filter.params;
for(var i=0, len=params.length; i<len; i++){
this.writeOgcExpression(params[i], node);
}
return node;
},
"PropertyIsNull": function(filter) {
var node = this.createElementNSPlus("ogc:PropertyIsNull");
this.writeNode("PropertyName", filter, node);
return node;
}
}
},
/**
* Method: getFilterType
*/
getFilterType: function(filter) {
var filterType = this.filterMap[filter.type];
if(!filterType) {
throw "Filter writing not supported for rule type: " + filter.type;
}
return filterType;
},
/**
* Property: filterMap
* {Object} Contains a member for each filter type. Values are node names
* for corresponding OGC Filter child elements.
*/
filterMap: {
"&&": "And",
"||": "Or",
"!": "Not",
"==": "PropertyIsEqualTo",
"!=": "PropertyIsNotEqualTo",
"<": "PropertyIsLessThan",
">": "PropertyIsGreaterThan",
"<=": "PropertyIsLessThanOrEqualTo",
">=": "PropertyIsGreaterThanOrEqualTo",
"..": "PropertyIsBetween",
"~": "PropertyIsLike",
"NULL": "PropertyIsNull",
"BBOX": "BBOX",
"DWITHIN": "DWITHIN",
"WITHIN": "WITHIN",
"CONTAINS": "CONTAINS",
"INTERSECTS": "INTERSECTS",
"FID": "_featureIds"
},
CLASS_NAME: "OpenLayers.Format.Filter.v1"
});
/* ======================================================================
OpenLayers/Format/Filter/v1_0_0.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Format/GML/v2.js
* @requires OpenLayers/Format/Filter/v1.js
*/
/**
* Class: OpenLayers.Format.Filter.v1_0_0
* Write ogc:Filter version 1.0.0.
*
* Inherits from:
* - <OpenLayers.Format.GML.v2>
* - <OpenLayers.Format.Filter.v1>
*/
OpenLayers.Format.Filter.v1_0_0 = OpenLayers.Class(
OpenLayers.Format.GML.v2, OpenLayers.Format.Filter.v1, {
/**
* Constant: VERSION
* {String} 1.0.0
*/
VERSION: "1.0.0",
/**
* Property: schemaLocation
* {String} http://www.opengis.net/ogc/filter/1.0.0/filter.xsd
*/
schemaLocation: "http://www.opengis.net/ogc/filter/1.0.0/filter.xsd",
/**
* Constructor: OpenLayers.Format.Filter.v1_0_0
* Instances of this class are not created directly. Use the
* <OpenLayers.Format.Filter> constructor instead.
*
* Parameters:
* options - {Object} An optional object whose properties will be set on
* this instance.
*/
initialize: function(options) {
OpenLayers.Format.GML.v2.prototype.initialize.apply(
this, [options]
);
},
/**
* Property: readers
* Contains public functions, grouped by namespace prefix, that will
* be applied when a namespaced node is found matching the function
* name. The function will be applied in the scope of this parser
* with two arguments: the node being read and a context object passed
* from the parent.
*/
readers: {
"ogc": OpenLayers.Util.applyDefaults({
"PropertyIsEqualTo": function(node, obj) {
var filter = new OpenLayers.Filter.Comparison({
type: OpenLayers.Filter.Comparison.EQUAL_TO
});
this.readChildNodes(node, filter);
obj.filters.push(filter);
},
"PropertyIsNotEqualTo": function(node, obj) {
var filter = new OpenLayers.Filter.Comparison({
type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO
});
this.readChildNodes(node, filter);
obj.filters.push(filter);
},
"PropertyIsLike": function(node, obj) {
var filter = new OpenLayers.Filter.Comparison({
type: OpenLayers.Filter.Comparison.LIKE
});
this.readChildNodes(node, filter);
var wildCard = node.getAttribute("wildCard");
var singleChar = node.getAttribute("singleChar");
var esc = node.getAttribute("escape");
filter.value2regex(wildCard, singleChar, esc);
obj.filters.push(filter);
}
}, OpenLayers.Format.Filter.v1.prototype.readers["ogc"]),
"gml": OpenLayers.Format.GML.v2.prototype.readers["gml"],
"feature": OpenLayers.Format.GML.v2.prototype.readers["feature"]
},
/**
* Property: writers
* As a compliment to the readers property, this structure contains public
* writing functions grouped by namespace alias and named like the
* node names they produce.
*/
writers: {
"ogc": OpenLayers.Util.applyDefaults({
"PropertyIsEqualTo": function(filter) {
var node = this.createElementNSPlus("ogc:PropertyIsEqualTo");
// no ogc:expression handling for PropertyName for now
this.writeNode("PropertyName", filter, node);
// handle Literals or Functions for now
this.writeOgcExpression(filter.value, node);
return node;
},
"PropertyIsNotEqualTo": function(filter) {
var node = this.createElementNSPlus("ogc:PropertyIsNotEqualTo");
// no ogc:expression handling for PropertyName for now
this.writeNode("PropertyName", filter, node);
// handle Literals or Functions for now
this.writeOgcExpression(filter.value, node);
return node;
},
"PropertyIsLike": function(filter) {
var node = this.createElementNSPlus("ogc:PropertyIsLike", {
attributes: {
wildCard: "*", singleChar: ".", escape: "!"
}
});
// no ogc:expression handling for now
this.writeNode("PropertyName", filter, node);
// convert regex string to ogc string
this.writeNode("Literal", filter.regex2value(), node);
return node;
},
"BBOX": function(filter) {
var node = this.createElementNSPlus("ogc:BBOX");
// PropertyName is mandatory in 1.0.0, but e.g. GeoServer also
// accepts filters without it. When this is used with
// OpenLayers.Protocol.WFS, OpenLayers.Format.WFST will set a
// missing filter.property to the geometryName that is
// configured with the protocol, which defaults to "the_geom".
// So the only way to omit this mandatory property is to not
// set the property on the filter and to set the geometryName
// on the WFS protocol to null. The latter also happens when
// the protocol is configured without a geometryName and a
// featureNS.
filter.property && this.writeNode("PropertyName", filter, node);
var box = this.writeNode("gml:Box", filter.value, node);
if(filter.projection) {
box.setAttribute("srsName", filter.projection);
}
return node;
}
}, OpenLayers.Format.Filter.v1.prototype.writers["ogc"]),
"gml": OpenLayers.Format.GML.v2.prototype.writers["gml"],
"feature": OpenLayers.Format.GML.v2.prototype.writers["feature"]
},
/**
* Method: writeSpatial
*
* Read a {<OpenLayers.Filter.Spatial>} filter and converts it into XML.
*
* Parameters:
* filter - {<OpenLayers.Filter.Spatial>} The filter.
* name - {String} Name of the generated XML element.
*
* Returns:
* {DOMElement} The created XML element.
*/
writeSpatial: function(filter, name) {
var node = this.createElementNSPlus("ogc:"+name);
this.writeNode("PropertyName", filter, node);
if(filter.value instanceof OpenLayers.Filter.Function) {
this.writeNode("Function", filter.value, node);
} else {
var child;
if(filter.value instanceof OpenLayers.Geometry) {
child = this.writeNode("feature:_geometry", filter.value).firstChild;
} else {
child = this.writeNode("gml:Box", filter.value);
}
if(filter.projection) {
child.setAttribute("srsName", filter.projection);
}
node.appendChild(child);
}
return node;
},
CLASS_NAME: "OpenLayers.Format.Filter.v1_0_0"
});
/* ======================================================================
OpenLayers/Format/WFST/v1_0_0.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Format/WFST/v1.js
* @requires OpenLayers/Format/Filter/v1_0_0.js
*/
/**
* Class: OpenLayers.Format.WFST.v1_0_0
* A format for creating WFS v1.0.0 transactions. Create a new instance with the
* <OpenLayers.Format.WFST.v1_0_0> constructor.
*
* Inherits from:
* - <OpenLayers.Format.Filter.v1_0_0>
* - <OpenLayers.Format.WFST.v1>
*/
OpenLayers.Format.WFST.v1_0_0 = OpenLayers.Class(
OpenLayers.Format.Filter.v1_0_0, OpenLayers.Format.WFST.v1, {
/**
* Property: version
* {String} WFS version number.
*/
version: "1.0.0",
/**
* APIProperty: srsNameInQuery
* {Boolean} If true the reference system is passed in Query requests
* via the "srsName" attribute to the "wfs:Query" element, this
* property defaults to false as it isn't WFS 1.0.0 compliant.
*/
srsNameInQuery: false,
/**
* Property: schemaLocations
* {Object} Properties are namespace aliases, values are schema locations.
*/
schemaLocations: {
"wfs": "http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd"
},
/**
* Constructor: OpenLayers.Format.WFST.v1_0_0
* A class for parsing and generating WFS v1.0.0 transactions.
*
* Parameters:
* options - {Object} Optional object whose properties will be set on the
* instance.
*
* Valid options properties:
* featureType - {String} Local (without prefix) feature typeName (required).
* featureNS - {String} Feature namespace (optional).
* featurePrefix - {String} Feature namespace alias (optional - only used
* if featureNS is provided). Default is 'feature'.
* geometryName - {String} Name of geometry attribute. Default is 'the_geom'.
*/
initialize: function(options) {
OpenLayers.Format.Filter.v1_0_0.prototype.initialize.apply(this, [options]);
OpenLayers.Format.WFST.v1.prototype.initialize.apply(this, [options]);
},
/**
* Method: readNode
* Shorthand for applying one of the named readers given the node
* namespace and local name. Readers take two args (node, obj) and
* generally extend or modify the second.
*
* Parameters:
* node - {DOMElement} The node to be read (required).
* obj - {Object} The object to be modified (optional).
* first - {Boolean} Should be set to true for the first node read. This
* is usually the readNode call in the read method. Without this being
* set, auto-configured properties will stick on subsequent reads.
*
* Returns:
* {Object} The input object, modified (or a new one if none was provided).
*/
readNode: function(node, obj, first) {
// Not the superclass, only the mixin classes inherit from
// Format.GML.v2. We need this because we don't want to get readNode
// from the superclass's superclass, which is OpenLayers.Format.XML.
return OpenLayers.Format.GML.v2.prototype.readNode.apply(this, arguments);
},
/**
* Property: readers
* Contains public functions, grouped by namespace prefix, that will
* be applied when a namespaced node is found matching the function
* name. The function will be applied in the scope of this parser
* with two arguments: the node being read and a context object passed
* from the parent.
*/
readers: {
"wfs": OpenLayers.Util.applyDefaults({
"WFS_TransactionResponse": function(node, obj) {
obj.insertIds = [];
obj.success = false;
this.readChildNodes(node, obj);
},
"InsertResult": function(node, container) {
var obj = {fids: []};
this.readChildNodes(node, obj);
container.insertIds = container.insertIds.concat(obj.fids);
},
"TransactionResult": function(node, obj) {
this.readChildNodes(node, obj);
},
"Status": function(node, obj) {
this.readChildNodes(node, obj);
},
"SUCCESS": function(node, obj) {
obj.success = true;
}
}, OpenLayers.Format.WFST.v1.prototype.readers["wfs"]),
"gml": OpenLayers.Format.GML.v2.prototype.readers["gml"],
"feature": OpenLayers.Format.GML.v2.prototype.readers["feature"],
"ogc": OpenLayers.Format.Filter.v1_0_0.prototype.readers["ogc"]
},
/**
* Property: writers
* As a compliment to the readers property, this structure contains public
* writing functions grouped by namespace alias and named like the
* node names they produce.
*/
writers: {
"wfs": OpenLayers.Util.applyDefaults({
"Query": function(options) {
options = OpenLayers.Util.extend({
featureNS: this.featureNS,
featurePrefix: this.featurePrefix,
featureType: this.featureType,
srsName: this.srsName,
srsNameInQuery: this.srsNameInQuery
}, options);
var prefix = options.featurePrefix;
var node = this.createElementNSPlus("wfs:Query", {
attributes: {
typeName: (options.featureNS ? prefix + ":" : "") +
options.featureType
}
});
if(options.srsNameInQuery && options.srsName) {
node.setAttribute("srsName", options.srsName);
}
if(options.featureNS) {
this.setAttributeNS(
node, this.namespaces.xmlns,
"xmlns:" + prefix, options.featureNS
);
}
if(options.propertyNames) {
for(var i=0,len = options.propertyNames.length; i<len; i++) {
this.writeNode(
"ogc:PropertyName",
{property: options.propertyNames[i]},
node
);
}
}
if(options.filter) {
this.setFilterProperty(options.filter);
this.writeNode("ogc:Filter", options.filter, node);
}
return node;
}
}, OpenLayers.Format.WFST.v1.prototype.writers["wfs"]),
"gml": OpenLayers.Format.GML.v2.prototype.writers["gml"],
"feature": OpenLayers.Format.GML.v2.prototype.writers["feature"],
"ogc": OpenLayers.Format.Filter.v1_0_0.prototype.writers["ogc"]
},
CLASS_NAME: "OpenLayers.Format.WFST.v1_0_0"
});
/* ======================================================================
OpenLayers/Protocol/WFS/v1_0_0.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Protocol/WFS/v1.js
* @requires OpenLayers/Format/WFST/v1_0_0.js
*/
/**
* Class: OpenLayers.Protocol.WFS.v1_0_0
* A WFS v1.0.0 protocol for vector layers. Create a new instance with the
* <OpenLayers.Protocol.WFS.v1_0_0> constructor.
*
* Inherits from:
* - <OpenLayers.Protocol.WFS.v1>
*/
OpenLayers.Protocol.WFS.v1_0_0 = OpenLayers.Class(OpenLayers.Protocol.WFS.v1, {
/**
* Property: version
* {String} WFS version number.
*/
version: "1.0.0",
/**
* Constructor: OpenLayers.Protocol.WFS.v1_0_0
* A class for giving layers WFS v1.0.0 protocol.
*
* Parameters:
* options - {Object} Optional object whose properties will be set on the
* instance.
*
* Valid options properties:
* featureType - {String} Local (without prefix) feature typeName (required).
* featureNS - {String} Feature namespace (optional).
* featurePrefix - {String} Feature namespace alias (optional - only used
* if featureNS is provided). Default is 'feature'.
* geometryName - {String} Name of geometry attribute. Default is 'the_geom'.
*/
CLASS_NAME: "OpenLayers.Protocol.WFS.v1_0_0"
});
/* ======================================================================
OpenLayers/Format/GML/v3.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Format/GML/Base.js
*/
/**
* Class: OpenLayers.Format.GML.v3
* Parses GML version 3.
*
* Inherits from:
* - <OpenLayers.Format.GML.Base>
*/
OpenLayers.Format.GML.v3 = OpenLayers.Class(OpenLayers.Format.GML.Base, {
/**
* Property: schemaLocation
* {String} Schema location for a particular minor version. The writers
* conform with the Simple Features Profile for GML.
*/
schemaLocation: "http://www.opengis.net/gml http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/1.0.0/gmlsf.xsd",
/**
* Property: curve
* {Boolean} Write gml:Curve instead of gml:LineString elements. This also
* affects the elements in multi-part geometries. Default is false.
* To write gml:Curve elements instead of gml:LineString, set curve
* to true in the options to the contstructor (cannot be changed after
* instantiation).
*/
curve: false,
/**
* Property: multiCurve
* {Boolean} Write gml:MultiCurve instead of gml:MultiLineString. Since
* the latter is deprecated in GML 3, the default is true. To write
* gml:MultiLineString instead of gml:MultiCurve, set multiCurve to
* false in the options to the constructor (cannot be changed after
* instantiation).
*/
multiCurve: true,
/**
* Property: surface
* {Boolean} Write gml:Surface instead of gml:Polygon elements. This also
* affects the elements in multi-part geometries. Default is false.
* To write gml:Surface elements instead of gml:Polygon, set surface
* to true in the options to the contstructor (cannot be changed after
* instantiation).
*/
surface: false,
/**
* Property: multiSurface
* {Boolean} Write gml:multiSurface instead of gml:MultiPolygon. Since
* the latter is deprecated in GML 3, the default is true. To write
* gml:MultiPolygon instead of gml:multiSurface, set multiSurface to
* false in the options to the constructor (cannot be changed after
* instantiation).
*/
multiSurface: true,
/**
* Constructor: OpenLayers.Format.GML.v3
* Create a parser for GML v3.
*
* Parameters:
* options - {Object} An optional object whose properties will be set on
* this instance.
*
* Valid options properties:
* featureType - {String} Local (without prefix) feature typeName (required).
* featureNS - {String} Feature namespace (required).
* geometryName - {String} Geometry element name.
*/
initialize: function(options) {
OpenLayers.Format.GML.Base.prototype.initialize.apply(this, [options]);
},
/**
* Property: readers
* Contains public functions, grouped by namespace prefix, that will
* be applied when a namespaced node is found matching the function
* name. The function will be applied in the scope of this parser
* with two arguments: the node being read and a context object passed
* from the parent.
*/
readers: {
"gml": OpenLayers.Util.applyDefaults({
"_inherit": function(node, obj, container) {
// SRSReferenceGroup attributes
var dim = parseInt(node.getAttribute("srsDimension"), 10) ||
(container && container.srsDimension);
if (dim) {
obj.srsDimension = dim;
}
},
"featureMembers": function(node, obj) {
this.readChildNodes(node, obj);
},
"Curve": function(node, container) {
var obj = {points: []};
this.readers.gml._inherit.apply(this, [node, obj, container]);
this.readChildNodes(node, obj);
if(!container.components) {
container.components = [];
}
container.components.push(
new OpenLayers.Geometry.LineString(obj.points)
);
},
"segments": function(node, obj) {
this.readChildNodes(node, obj);
},
"LineStringSegment": function(node, container) {
var obj = {};
this.readChildNodes(node, obj);
if(obj.points) {
Array.prototype.push.apply(container.points, obj.points);
}
},
"pos": function(node, obj) {
var str = this.getChildValue(node).replace(
this.regExes.trimSpace, ""
);
var coords = str.split(this.regExes.splitSpace);
var point;
if(this.xy) {
point = new OpenLayers.Geometry.Point(
coords[0], coords[1], coords[2]
);
} else {
point = new OpenLayers.Geometry.Point(
coords[1], coords[0], coords[2]
);
}
if(!!!obj.points) {
obj.points = [];
}
obj.points.push(point);
},
"posList": function(node, obj) {
var str = this.getChildValue(node).replace(
this.regExes.trimSpace, ""
);
var coords = str.split(this.regExes.splitSpace);
// The "dimension" attribute is from the GML 3.0.1 spec.
var dim = obj.srsDimension ||
parseInt(node.getAttribute("srsDimension") || node.getAttribute("dimension"), 10) || 2;
var j, x, y, z;
var numPoints = coords.length / dim;
var points = new Array(numPoints);
for(var i=0, len=coords.length; i<len; i += dim) {
x = coords[i];
y = coords[i+1];
z = (dim == 2) ? undefined : coords[i+2];
if (this.xy) {
points[i/dim] = new OpenLayers.Geometry.Point(x, y, z);
} else {
points[i/dim] = new OpenLayers.Geometry.Point(y, x, z);
}
}
obj.points = points;
},
"Surface": function(node, obj) {
this.readChildNodes(node, obj);
},
"patches": function(node, obj) {
this.readChildNodes(node, obj);
},
"PolygonPatch": function(node, obj) {
this.readers.gml.Polygon.apply(this, [node, obj]);
},
"exterior": function(node, container) {
var obj = {};
this.readChildNodes(node, obj);
container.outer = obj.components[0];
},
"interior": function(node, container) {
var obj = {};
this.readChildNodes(node, obj);
container.inner.push(obj.components[0]);
},
"MultiCurve": function(node, container) {
var obj = {components: []};
this.readers.gml._inherit.apply(this, [node, obj, container]);
this.readChildNodes(node, obj);
if(obj.components.length > 0) {
container.components = [
new OpenLayers.Geometry.MultiLineString(obj.components)
];
}
},
"curveMember": function(node, obj) {
this.readChildNodes(node, obj);
},
"MultiSurface": function(node, container) {
var obj = {components: []};
this.readers.gml._inherit.apply(this, [node, obj, container]);
this.readChildNodes(node, obj);
if(obj.components.length > 0) {
container.components = [
new OpenLayers.Geometry.MultiPolygon(obj.components)
];
}
},
"surfaceMember": function(node, obj) {
this.readChildNodes(node, obj);
},
"surfaceMembers": function(node, obj) {
this.readChildNodes(node, obj);
},
"pointMembers": function(node, obj) {
this.readChildNodes(node, obj);
},
"lineStringMembers": function(node, obj) {
this.readChildNodes(node, obj);
},
"polygonMembers": function(node, obj) {
this.readChildNodes(node, obj);
},
"geometryMembers": function(node, obj) {
this.readChildNodes(node, obj);
},
"Envelope": function(node, container) {
var obj = {points: new Array(2)};
this.readChildNodes(node, obj);
if(!container.components) {
container.components = [];
}
var min = obj.points[0];
var max = obj.points[1];
container.components.push(
new OpenLayers.Bounds(min.x, min.y, max.x, max.y)
);
},
"lowerCorner": function(node, container) {
var obj = {};
this.readers.gml.pos.apply(this, [node, obj]);
container.points[0] = obj.points[0];
},
"upperCorner": function(node, container) {
var obj = {};
this.readers.gml.pos.apply(this, [node, obj]);
container.points[1] = obj.points[0];
}
}, OpenLayers.Format.GML.Base.prototype.readers["gml"]),
"feature": OpenLayers.Format.GML.Base.prototype.readers["feature"],
"wfs": OpenLayers.Format.GML.Base.prototype.readers["wfs"]
},
/**
* Method: write
*
* Parameters:
* features - {Array(<OpenLayers.Feature.Vector>) | OpenLayers.Feature.Vector}
* An array of features or a single feature.
*
* Returns:
* {String} Given an array of features, a doc with a gml:featureMembers
* element will be returned. Given a single feature, a doc with a
* gml:featureMember element will be returned.
*/
write: function(features) {
var name;
if(OpenLayers.Util.isArray(features)) {
name = "featureMembers";
} else {
name = "featureMember";
}
var root = this.writeNode("gml:" + name, features);
this.setAttributeNS(
root, this.namespaces["xsi"],
"xsi:schemaLocation", this.schemaLocation
);
return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
},
/**
* Property: writers
* As a compliment to the readers property, this structure contains public
* writing functions grouped by namespace alias and named like the
* node names they produce.
*/
writers: {
"gml": OpenLayers.Util.applyDefaults({
"featureMembers": function(features) {
var node = this.createElementNSPlus("gml:featureMembers");
for(var i=0, len=features.length; i<len; ++i) {
this.writeNode("feature:_typeName", features[i], node);
}
return node;
},
"Point": function(geometry) {
var node = this.createElementNSPlus("gml:Point");
this.writeNode("pos", geometry, node);
return node;
},
"pos": function(point) {
// only 2d for simple features profile
var pos = (this.xy) ?
(point.x + " " + point.y) : (point.y + " " + point.x);
return this.createElementNSPlus("gml:pos", {
value: pos
});
},
"LineString": function(geometry) {
var node = this.createElementNSPlus("gml:LineString");
this.writeNode("posList", geometry.components, node);
return node;
},
"Curve": function(geometry) {
var node = this.createElementNSPlus("gml:Curve");
this.writeNode("segments", geometry, node);
return node;
},
"segments": function(geometry) {
var node = this.createElementNSPlus("gml:segments");
this.writeNode("LineStringSegment", geometry, node);
return node;
},
"LineStringSegment": function(geometry) {
var node = this.createElementNSPlus("gml:LineStringSegment");
this.writeNode("posList", geometry.components, node);
return node;
},
"posList": function(points) {
// only 2d for simple features profile
var len = points.length;
var parts = new Array(len);
var point;
for(var i=0; i<len; ++i) {
point = points[i];
if(this.xy) {
parts[i] = point.x + " " + point.y;
} else {
parts[i] = point.y + " " + point.x;
}
}
return this.createElementNSPlus("gml:posList", {
value: parts.join(" ")
});
},
"Surface": function(geometry) {
var node = this.createElementNSPlus("gml:Surface");
this.writeNode("patches", geometry, node);
return node;
},
"patches": function(geometry) {
var node = this.createElementNSPlus("gml:patches");
this.writeNode("PolygonPatch", geometry, node);
return node;
},
"PolygonPatch": function(geometry) {
var node = this.createElementNSPlus("gml:PolygonPatch", {
attributes: {interpolation: "planar"}
});
this.writeNode("exterior", geometry.components[0], node);
for(var i=1, len=geometry.components.length; i<len; ++i) {
this.writeNode(
"interior", geometry.components[i], node
);
}
return node;
},
"Polygon": function(geometry) {
var node = this.createElementNSPlus("gml:Polygon");
this.writeNode("exterior", geometry.components[0], node);
for(var i=1, len=geometry.components.length; i<len; ++i) {
this.writeNode(
"interior", geometry.components[i], node
);
}
return node;
},
"exterior": function(ring) {
var node = this.createElementNSPlus("gml:exterior");
this.writeNode("LinearRing", ring, node);
return node;
},
"interior": function(ring) {
var node = this.createElementNSPlus("gml:interior");
this.writeNode("LinearRing", ring, node);
return node;
},
"LinearRing": function(ring) {
var node = this.createElementNSPlus("gml:LinearRing");
this.writeNode("posList", ring.components, node);
return node;
},
"MultiCurve": function(geometry) {
var node = this.createElementNSPlus("gml:MultiCurve");
var components = geometry.components || [geometry];
for(var i=0, len=components.length; i<len; ++i) {
this.writeNode("curveMember", components[i], node);
}
return node;
},
"curveMember": function(geometry) {
var node = this.createElementNSPlus("gml:curveMember");
if(this.curve) {
this.writeNode("Curve", geometry, node);
} else {
this.writeNode("LineString", geometry, node);
}
return node;
},
"MultiSurface": function(geometry) {
var node = this.createElementNSPlus("gml:MultiSurface");
var components = geometry.components || [geometry];
for(var i=0, len=components.length; i<len; ++i) {
this.writeNode("surfaceMember", components[i], node);
}
return node;
},
"surfaceMember": function(polygon) {
var node = this.createElementNSPlus("gml:surfaceMember");
if(this.surface) {
this.writeNode("Surface", polygon, node);
} else {
this.writeNode("Polygon", polygon, node);
}
return node;
},
"Envelope": function(bounds) {
var node = this.createElementNSPlus("gml:Envelope");
this.writeNode("lowerCorner", bounds, node);
this.writeNode("upperCorner", bounds, node);
// srsName attribute is required for gml:Envelope
if(this.srsName) {
node.setAttribute("srsName", this.srsName);
}
return node;
},
"lowerCorner": function(bounds) {
// only 2d for simple features profile
var pos = (this.xy) ?
(bounds.left + " " + bounds.bottom) :
(bounds.bottom + " " + bounds.left);
return this.createElementNSPlus("gml:lowerCorner", {
value: pos
});
},
"upperCorner": function(bounds) {
// only 2d for simple features profile
var pos = (this.xy) ?
(bounds.right + " " + bounds.top) :
(bounds.top + " " + bounds.right);
return this.createElementNSPlus("gml:upperCorner", {
value: pos
});
}
}, OpenLayers.Format.GML.Base.prototype.writers["gml"]),
"feature": OpenLayers.Format.GML.Base.prototype.writers["feature"],
"wfs": OpenLayers.Format.GML.Base.prototype.writers["wfs"]
},
/**
* Method: setGeometryTypes
* Sets the <geometryTypes> mapping.
*/
setGeometryTypes: function() {
this.geometryTypes = {
"OpenLayers.Geometry.Point": "Point",
"OpenLayers.Geometry.MultiPoint": "MultiPoint",
"OpenLayers.Geometry.LineString": (this.curve === true) ? "Curve": "LineString",
"OpenLayers.Geometry.MultiLineString": (this.multiCurve === false) ? "MultiLineString" : "MultiCurve",
"OpenLayers.Geometry.Polygon": (this.surface === true) ? "Surface" : "Polygon",
"OpenLayers.Geometry.MultiPolygon": (this.multiSurface === false) ? "MultiPolygon" : "MultiSurface",
"OpenLayers.Geometry.Collection": "GeometryCollection"
};
},
CLASS_NAME: "OpenLayers.Format.GML.v3"
});
/* ======================================================================
OpenLayers/Format/Filter/v1_1_0.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Format/Filter/v1.js
* @requires OpenLayers/Format/GML/v3.js
*/
/**
* Class: OpenLayers.Format.Filter.v1_1_0
* Write ogc:Filter version 1.1.0.
*
* Differences from the v1.0.0 parser:
* - uses GML v3 instead of GML v2
* - reads matchCase attribute on ogc:PropertyIsEqual and
* ogc:PropertyIsNotEqual elements.
* - writes matchCase attribute from comparison filters of type EQUAL_TO,
* NOT_EQUAL_TO and LIKE.
*
* Inherits from:
* - <OpenLayers.Format.GML.v3>
* - <OpenLayers.Format.Filter.v1>
*/
OpenLayers.Format.Filter.v1_1_0 = OpenLayers.Class(
OpenLayers.Format.GML.v3, OpenLayers.Format.Filter.v1, {
/**
* Constant: VERSION
* {String} 1.1.0
*/
VERSION: "1.1.0",
/**
* Property: schemaLocation
* {String} http://www.opengis.net/ogc/filter/1.1.0/filter.xsd
*/
schemaLocation: "http://www.opengis.net/ogc/filter/1.1.0/filter.xsd",
/**
* Constructor: OpenLayers.Format.Filter.v1_1_0
* Instances of this class are not created directly. Use the
* <OpenLayers.Format.Filter> constructor instead.
*
* Parameters:
* options - {Object} An optional object whose properties will be set on
* this instance.
*/
initialize: function(options) {
OpenLayers.Format.GML.v3.prototype.initialize.apply(
this, [options]
);
},
/**
* Property: readers
* Contains public functions, grouped by namespace prefix, that will
* be applied when a namespaced node is found matching the function
* name. The function will be applied in the scope of this parser
* with two arguments: the node being read and a context object passed
* from the parent.
*/
readers: {
"ogc": OpenLayers.Util.applyDefaults({
"PropertyIsEqualTo": function(node, obj) {
var matchCase = node.getAttribute("matchCase");
var filter = new OpenLayers.Filter.Comparison({
type: OpenLayers.Filter.Comparison.EQUAL_TO,
matchCase: !(matchCase === "false" || matchCase === "0")
});
this.readChildNodes(node, filter);
obj.filters.push(filter);
},
"PropertyIsNotEqualTo": function(node, obj) {
var matchCase = node.getAttribute("matchCase");
var filter = new OpenLayers.Filter.Comparison({
type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
matchCase: !(matchCase === "false" || matchCase === "0")
});
this.readChildNodes(node, filter);
obj.filters.push(filter);
},
"PropertyIsLike": function(node, obj) {
var filter = new OpenLayers.Filter.Comparison({
type: OpenLayers.Filter.Comparison.LIKE
});
this.readChildNodes(node, filter);
var wildCard = node.getAttribute("wildCard");
var singleChar = node.getAttribute("singleChar");
var esc = node.getAttribute("escapeChar");
filter.value2regex(wildCard, singleChar, esc);
obj.filters.push(filter);
}
}, OpenLayers.Format.Filter.v1.prototype.readers["ogc"]),
"gml": OpenLayers.Format.GML.v3.prototype.readers["gml"],
"feature": OpenLayers.Format.GML.v3.prototype.readers["feature"]
},
/**
* Property: writers
* As a compliment to the readers property, this structure contains public
* writing functions grouped by namespace alias and named like the
* node names they produce.
*/
writers: {
"ogc": OpenLayers.Util.applyDefaults({
"PropertyIsEqualTo": function(filter) {
var node = this.createElementNSPlus("ogc:PropertyIsEqualTo", {
attributes: {matchCase: filter.matchCase}
});
// no ogc:expression handling for PropertyName for now
this.writeNode("PropertyName", filter, node);
// handle Literals or Functions for now
this.writeOgcExpression(filter.value, node);
return node;
},
"PropertyIsNotEqualTo": function(filter) {
var node = this.createElementNSPlus("ogc:PropertyIsNotEqualTo", {
attributes: {matchCase: filter.matchCase}
});
// no ogc:expression handling for PropertyName for now
this.writeNode("PropertyName", filter, node);
// handle Literals or Functions for now
this.writeOgcExpression(filter.value, node);
return node;
},
"PropertyIsLike": function(filter) {
var node = this.createElementNSPlus("ogc:PropertyIsLike", {
attributes: {
matchCase: filter.matchCase,
wildCard: "*", singleChar: ".", escapeChar: "!"
}
});
// no ogc:expression handling for now
this.writeNode("PropertyName", filter, node);
// convert regex string to ogc string
this.writeNode("Literal", filter.regex2value(), node);
return node;
},
"BBOX": function(filter) {
var node = this.createElementNSPlus("ogc:BBOX");
// PropertyName is optional in 1.1.0
filter.property && this.writeNode("PropertyName", filter, node);
var box = this.writeNode("gml:Envelope", filter.value);
if(filter.projection) {
box.setAttribute("srsName", filter.projection);
}
node.appendChild(box);
return node;
},
"SortBy": function(sortProperties) {
var node = this.createElementNSPlus("ogc:SortBy");
for (var i=0,l=sortProperties.length;i<l;i++) {
this.writeNode(
"ogc:SortProperty",
sortProperties[i],
node
);
}
return node;
},
"SortProperty": function(sortProperty) {
var node = this.createElementNSPlus("ogc:SortProperty");
this.writeNode(
"ogc:PropertyName",
sortProperty,
node
);
this.writeNode(
"ogc:SortOrder",
(sortProperty.order == 'DESC') ? 'DESC' : 'ASC',
node
);
return node;
},
"SortOrder": function(value) {
var node = this.createElementNSPlus("ogc:SortOrder", {
value: value
});
return node;
}
}, OpenLayers.Format.Filter.v1.prototype.writers["ogc"]),
"gml": OpenLayers.Format.GML.v3.prototype.writers["gml"],
"feature": OpenLayers.Format.GML.v3.prototype.writers["feature"]
},
/**
* Method: writeSpatial
*
* Read a {<OpenLayers.Filter.Spatial>} filter and converts it into XML.
*
* Parameters:
* filter - {<OpenLayers.Filter.Spatial>} The filter.
* name - {String} Name of the generated XML element.
*
* Returns:
* {DOMElement} The created XML element.
*/
writeSpatial: function(filter, name) {
var node = this.createElementNSPlus("ogc:"+name);
this.writeNode("PropertyName", filter, node);
if(filter.value instanceof OpenLayers.Filter.Function) {
this.writeNode("Function", filter.value, node);
} else {
var child;
if(filter.value instanceof OpenLayers.Geometry) {
child = this.writeNode("feature:_geometry", filter.value).firstChild;
} else {
child = this.writeNode("gml:Envelope", filter.value);
}
if(filter.projection) {
child.setAttribute("srsName", filter.projection);
}
node.appendChild(child);
}
return node;
},
CLASS_NAME: "OpenLayers.Format.Filter.v1_1_0"
});
/* ======================================================================
OpenLayers/Format/OWSCommon.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Format/XML/VersionedOGC.js
*/
/**
* Class: OpenLayers.Format.OWSCommon
* Read OWSCommon. Create a new instance with the <OpenLayers.Format.OWSCommon>
* constructor.
*
* Inherits from:
* - <OpenLayers.Format.XML.VersionedOGC>
*/
OpenLayers.Format.OWSCommon = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
/**
* APIProperty: defaultVersion
* {String} Version number to assume if none found. Default is "1.0.0".
*/
defaultVersion: "1.0.0",
/**
* Constructor: OpenLayers.Format.OWSCommon
* Create a new parser for OWSCommon.
*
* Parameters:
* options - {Object} An optional object whose properties will be set on
* this instance.
*/
/**
* Method: getVersion
* Returns the version to use. Subclasses can override this function
* if a different version detection is needed.
*
* Parameters:
* root - {DOMElement}
* options - {Object} Optional configuration object.
*
* Returns:
* {String} The version to use.
*/
getVersion: function(root, options) {
var version = this.version;
if(!version) {
// remember version does not correspond to the OWS version
// it corresponds to the WMS/WFS/WCS etc. request version
var uri = root.getAttribute("xmlns:ows");
// the above will fail if the namespace prefix is different than
// ows and if the namespace is declared on a different element
if (uri && uri.substring(uri.lastIndexOf("/")+1) === "1.1") {
version ="1.1.0";
}
if(!version) {
version = this.defaultVersion;
}
}
return version;
},
/**
* APIMethod: read
* Read an OWSCommon document and return an object.
*
* Parameters:
* data - {String | DOMElement} Data to read.
* options - {Object} Options for the reader.
*
* Returns:
* {Object} An object representing the structure of the document.
*/
CLASS_NAME: "OpenLayers.Format.OWSCommon"
});
/* ======================================================================
OpenLayers/Format/OWSCommon/v1.js
====================================================================== */
/* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the 2-clause BSD license.
* See license.txt in the OpenLayers distribution or repository for the
* full text of the license. */
/**
* @requires OpenLayers/Format/OWSCommon.js
*/
/**
* Class: OpenLayers.Format.OWSCommon.v1
* Common readers and writers for OWSCommon v1.X formats
*
* Inherits from:
* - <OpenLayers.Format.XML>
*/
OpenLayers.Format.OWSCommon.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
/**
* Property: regExes
* Compiled regular expressions for manipulating strings.
*/
regExes: {
trimSpace: (/^\s*|\s*$/g),
removeSpace: (/\s*/g),
splitSpace: (/\s+/),
trimComma: (/\s*,\s*/g)
},
/**
* Method: read
*
* Parameters:
* data - {DOMElement} An OWSCommon document element.
* options - {Object} Options for the reader.
*
* Returns:
* {Object} An object representing the OWSCommon document.
*/
read: function(data, options) {
options = OpenLayers.Util.applyDefaults(options, this.options);
var ows = {};
this.readChildNodes(data, ows);
return ows;
},
/**
* Property: readers
* Contains public functions, grouped by namespace prefix, that will
* be applied when a namespaced node is found matching the function
* name. The function will be applied in the scope of this parser
* with two arguments: the node being read and a context object passed
* from the parent.
*/
readers: {
"ows": {
"Exception": function(node, exceptionReport) {
var exception = {
code: node.getAttribute('exceptionCode'),
locator: node.getAttribute('locator'),
texts: []
};
exceptionReport.exceptions.push(exception);
this.readChildNodes(node, exception);
},
"ExceptionText": function(node, exception) {
var text = this.getChildValue(node);
exception.texts.push(text);
},
"ServiceIdentification": function(node, obj) {
obj.serviceIdentification = {};
this.readChildNodes(node, obj.serviceIdentification);
},
"Title": function(node, obj) {
obj.title = this.getChildValue(node);
},
"Abstract": function(node, serviceIdentification) {
serviceIdentification["abstract"] = this.getChildValue(node);
},
"Keywords": function(node, serviceIdentification) {
serviceIdentification.keywords = {};
this.readChildNodes(node, serviceIdentification.keywords);
},
"Keyword": function(node, keywords) {
keywords[this.getChildValue(node)] = true;
},
"ServiceType": function(node, serviceIdentification) {
serviceIdentification.serviceType = {
codeSpace: node.getAttribute('codeSpace'),
value: this.getChildValue(node)};
},
"ServiceTypeVersion": function(node, serviceIdentification) {
serviceIdentification.serviceTypeVersion = this.getChildValue(node);
},
"Fees": function(node, serviceIdentification) {
serviceIdentification.fees = this.getChildValue(node);
},
"AccessConstraints": function(node, serviceIdentification) {
serviceIdentification.accessConstraints =
this.getChildValue(node);
},
"ServiceProvider": function(node, obj) {
obj.serviceProvider = {};
this.readChildNodes(node, obj.serviceProvider);
},
"ProviderName": function(node, serviceProvider) {
serviceProvider.providerName = this.getChildValue(node);
},
"ProviderSite": function(node, serviceProvider) {
serviceProvider.providerSite = this.getAttributeNS(node,
this.namespaces.xlink, "href");
},
"ServiceContact": function(node, serviceProvider) {
serviceProvider.serviceContact = {};
this.readChildNodes(node, serviceProvider.serviceContact);
},
"IndividualName": function(node, serviceContact) {
serviceContact.individualName = this.getChildValue(node);
},
"PositionName": function(node, serviceContact) {
serviceContact.positionName = this.getChildValue(node);
},
"ContactInfo": function(node, serviceContact) {
serviceContact.contactInfo = {};
this.readChildNodes(node, serviceContact.contactInfo);
},
"Phone": function(node, contactInfo) {
contactInfo.phone = {};
this.readChildNodes(node, contactInfo.phone);
},
"Voice": function(node, phone) {
phone.voice = this.getChildValue(node);
},
"Facsimile": function(node, phone) {
phone.facsimile = this.getChildValue(node);
},
"Address": function(node, contactInfo) {
contactInfo.address = {};
this.readChildNodes(node, contactInfo.address);
},
"DeliveryPoint": function(node, address) {
address.deliveryPoint = this.getChildValue(node);
},
"City": function(node, address) {
address.city = this.getChildValue(node);
},
"AdministrativeArea": function(node, address) {
address.administrativeArea = this.getChildValue(node);
},
"PostalCode": function(node, address) {
address.postalCode = this.getChildValue(node);
},
"Country": function(node, address) {
address.country = this.getChildValue(node);
},
"ElectronicMailAddress": function(node, address) {
address.electronicMailAddress = this.getChildValue(node);
},
"Role": function(node, serviceContact) {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment