]> ToastFreeware Gitweb - philipp/winterrodeln/mediawiki_extensions/wrmap.git/blob - openlayers/OpenLayers.js
8d47f6dc06ce5d8132c7cb738d7c72d8e672fc13
[philipp/winterrodeln/mediawiki_extensions/wrmap.git] / openlayers / OpenLayers.js
1 /*
2
3   OpenLayers.js -- OpenLayers Map Viewer Library
4
5   Copyright (c) 2006-2015 by OpenLayers Contributors
6   Published under the 2-clause BSD license.
7   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.
8
9   Includes compressed code under the following licenses:
10
11   (For uncompressed versions of the code used, please see the
12   OpenLayers Github repository: <https://github.com/openlayers/ol2>)
13
14 */
15
16 /**
17  * Contains XMLHttpRequest.js <http://code.google.com/p/xmlhttprequest/>
18  * Copyright 2007 Sergey Ilinsky (http://www.ilinsky.com)
19  *
20  * Licensed under the Apache License, Version 2.0 (the "License");
21  * you may not use this file except in compliance with the License.
22  * You may obtain a copy of the License at
23  * http://www.apache.org/licenses/LICENSE-2.0
24  */
25
26 /**
27  * OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is
28  * Copyright (c) 2006, Yahoo! Inc.
29  * All rights reserved.
30  *
31  * Redistribution and use of this software in source and binary forms, with or
32  * without modification, are permitted provided that the following conditions
33  * are met:
34  *
35  * * Redistributions of source code must retain the above copyright notice,
36  *   this list of conditions and the following disclaimer.
37  *
38  * * Redistributions in binary form must reproduce the above copyright notice,
39  *   this list of conditions and the following disclaimer in the documentation
40  *   and/or other materials provided with the distribution.
41  *
42  * * Neither the name of Yahoo! Inc. nor the names of its contributors may be
43  *   used to endorse or promote products derived from this software without
44  *   specific prior written permission of Yahoo! Inc.
45  *
46  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
47  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
48  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
49  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
50  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
51  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
52  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
53  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
54  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
55  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
56  * POSSIBILITY OF SUCH DAMAGE.
57  */
58 /* ======================================================================
59     OpenLayers/SingleFile.js
60    ====================================================================== */
61
62 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
63  * full list of contributors). Published under the 2-clause BSD license.
64  * See license.txt in the OpenLayers distribution or repository for the
65  * full text of the license. */
66
67 var OpenLayers = {
68     /**
69      * Constant: VERSION_NUMBER
70      */
71     VERSION_NUMBER: "Release 2.14 dev",
72
73     /**
74      * Constant: singleFile
75      * TODO: remove this in 3.0 when we stop supporting build profiles that
76      * include OpenLayers.js
77      */
78     singleFile: true,
79
80     /**
81      * Method: _getScriptLocation
82      * Return the path to this script. This is also implemented in
83      * OpenLayers.js
84      *
85      * Returns:
86      * {String} Path to this script
87      */
88     _getScriptLocation: (function() {
89         var r = new RegExp("(^|(.*?\\/))(OpenLayers[^\\/]*?\\.js)(\\?|$)"),
90             s = document.getElementsByTagName('script'),
91             src, m, l = "";
92         for(var i=0, len=s.length; i<len; i++) {
93             src = s[i].getAttribute('src');
94             if(src) {
95                 m = src.match(r);
96                 if(m) {
97                     l = m[1];
98                     break;
99                 }
100             }
101         }
102         return (function() { return l; });
103     })(),
104     
105     /**
106      * Property: ImgPath
107      * {String} Set this to the path where control images are stored, a path  
108      * given here must end with a slash. If set to '' (which is the default) 
109      * OpenLayers will use its script location + "img/".
110      * 
111      * You will need to set this property when you have a singlefile build of 
112      * OpenLayers that either is not named "OpenLayers.js" or if you move
113      * the file in a way such that the image directory cannot be derived from 
114      * the script location.
115      * 
116      * If your custom OpenLayers build is named "my-custom-ol.js" and the images
117      * of OpenLayers are in a folder "/resources/external/images/ol" a correct
118      * way of including OpenLayers in your HTML would be:
119      * 
120      * (code)
121      *   <script src="/path/to/my-custom-ol.js" type="text/javascript"></script>
122      *   <script type="text/javascript">
123      *      // tell OpenLayers where the control images are
124      *      // remember the trailing slash
125      *      OpenLayers.ImgPath = "/resources/external/images/ol/";
126      *   </script>
127      * (end code)
128      * 
129      * Please remember that when your OpenLayers script is not named 
130      * "OpenLayers.js" you will have to make sure that the default theme is 
131      * loaded into the page by including an appropriate <link>-tag, 
132      * e.g.:
133      * 
134      * (code)
135      *   <link rel="stylesheet" href="/path/to/default/style.css"  type="text/css">
136      * (end code)
137      */
138     ImgPath : ''
139 };
140 /* ======================================================================
141     OpenLayers/BaseTypes/Class.js
142    ====================================================================== */
143
144 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
145  * full list of contributors). Published under the 2-clause BSD license.
146  * See license.txt in the OpenLayers distribution or repository for the
147  * full text of the license. */
148
149 /**
150  * @requires OpenLayers/SingleFile.js
151  */
152
153 /**
154  * Constructor: OpenLayers.Class
155  * Base class used to construct all other classes. Includes support for 
156  *     multiple inheritance. 
157  *     
158  * This constructor is new in OpenLayers 2.5.  At OpenLayers 3.0, the old 
159  *     syntax for creating classes and dealing with inheritance 
160  *     will be removed.
161  * 
162  * To create a new OpenLayers-style class, use the following syntax:
163  * (code)
164  *     var MyClass = OpenLayers.Class(prototype);
165  * (end)
166  *
167  * To create a new OpenLayers-style class with multiple inheritance, use the
168  *     following syntax:
169  * (code)
170  *     var MyClass = OpenLayers.Class(Class1, Class2, prototype);
171  * (end)
172  * 
173  * Note that instanceof reflection will only reveal Class1 as superclass.
174  *
175  */
176 OpenLayers.Class = function() {
177     var len = arguments.length;
178     var P = arguments[0];
179     var F = arguments[len-1];
180
181     var C = typeof F.initialize == "function" ?
182         F.initialize :
183         function(){ P.prototype.initialize.apply(this, arguments); };
184
185     if (len > 1) {
186         var newArgs = [C, P].concat(
187                 Array.prototype.slice.call(arguments).slice(1, len-1), F);
188         OpenLayers.inherit.apply(null, newArgs);
189     } else {
190         C.prototype = F;
191     }
192     return C;
193 };
194
195 /**
196  * Function: OpenLayers.inherit
197  *
198  * Parameters:
199  * C - {Object} the class that inherits
200  * P - {Object} the superclass to inherit from
201  *
202  * In addition to the mandatory C and P parameters, an arbitrary number of
203  * objects can be passed, which will extend C.
204  */
205 OpenLayers.inherit = function(C, P) {
206    var F = function() {};
207    F.prototype = P.prototype;
208    C.prototype = new F;
209    var i, l, o;
210    for(i=2, l=arguments.length; i<l; i++) {
211        o = arguments[i];
212        if(typeof o === "function") {
213            o = o.prototype;
214        }
215        OpenLayers.Util.extend(C.prototype, o);
216    }
217 };
218
219 /**
220  * APIFunction: extend
221  * Copy all properties of a source object to a destination object.  Modifies
222  *     the passed in destination object.  Any properties on the source object
223  *     that are set to undefined will not be (re)set on the destination object.
224  *
225  * Parameters:
226  * destination - {Object} The object that will be modified
227  * source - {Object} The object with properties to be set on the destination
228  *
229  * Returns:
230  * {Object} The destination object.
231  */
232 OpenLayers.Util = OpenLayers.Util || {};
233 OpenLayers.Util.extend = function(destination, source) {
234     destination = destination || {};
235     if (source) {
236         for (var property in source) {
237             var value = source[property];
238             if (value !== undefined) {
239                 destination[property] = value;
240             }
241         }
242
243         /**
244          * IE doesn't include the toString property when iterating over an object's
245          * properties with the for(property in object) syntax.  Explicitly check if
246          * the source has its own toString property.
247          */
248
249         /*
250          * FF/Windows < 2.0.0.13 reports "Illegal operation on WrappedNative
251          * prototype object" when calling hawOwnProperty if the source object
252          * is an instance of window.Event.
253          */
254
255         var sourceIsEvt = typeof window.Event == "function"
256                           && source instanceof window.Event;
257
258         if (!sourceIsEvt
259            && source.hasOwnProperty && source.hasOwnProperty("toString")) {
260             destination.toString = source.toString;
261         }
262     }
263     return destination;
264 };
265 /* ======================================================================
266     OpenLayers/BaseTypes.js
267    ====================================================================== */
268
269 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
270  * full list of contributors). Published under the 2-clause BSD license.
271  * See license.txt in the OpenLayers distribution or repository for the
272  * full text of the license. */
273
274 /**
275  * @requires OpenLayers/SingleFile.js
276  */
277
278 /** 
279  * Header: OpenLayers Base Types
280  * OpenLayers custom string, number and function functions are described here.
281  */
282
283 /**
284  * Namespace: OpenLayers.String
285  * Contains convenience functions for string manipulation.
286  */
287 OpenLayers.String = {
288
289     /**
290      * APIFunction: startsWith
291      * Test whether a string starts with another string. 
292      * 
293      * Parameters:
294      * str - {String} The string to test.
295      * sub - {String} The substring to look for.
296      *  
297      * Returns:
298      * {Boolean} The first string starts with the second.
299      */
300     startsWith: function(str, sub) {
301         return (str.indexOf(sub) == 0);
302     },
303
304     /**
305      * APIFunction: contains
306      * Test whether a string contains another string.
307      * 
308      * Parameters:
309      * str - {String} The string to test.
310      * sub - {String} The substring to look for.
311      * 
312      * Returns:
313      * {Boolean} The first string contains the second.
314      */
315     contains: function(str, sub) {
316         return (str.indexOf(sub) != -1);
317     },
318     
319     /**
320      * APIFunction: trim
321      * Removes leading and trailing whitespace characters from a string.
322      * 
323      * Parameters:
324      * str - {String} The (potentially) space padded string.  This string is not
325      *     modified.
326      * 
327      * Returns:
328      * {String} A trimmed version of the string with all leading and 
329      *     trailing spaces removed.
330      */
331     trim: function(str) {
332         return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
333     },
334     
335     /**
336      * APIFunction: camelize
337      * Camel-case a hyphenated string. 
338      *     Ex. "chicken-head" becomes "chickenHead", and
339      *     "-chicken-head" becomes "ChickenHead".
340      *
341      * Parameters:
342      * str - {String} The string to be camelized.  The original is not modified.
343      * 
344      * Returns:
345      * {String} The string, camelized
346      */
347     camelize: function(str) {
348         var oStringList = str.split('-');
349         var camelizedString = oStringList[0];
350         for (var i=1, len=oStringList.length; i<len; i++) {
351             var s = oStringList[i];
352             camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
353         }
354         return camelizedString;
355     },
356     
357     /**
358      * APIFunction: format
359      * Given a string with tokens in the form ${token}, return a string
360      *     with tokens replaced with properties from the given context
361      *     object.  Represent a literal "${" by doubling it, e.g. "${${".
362      *
363      * Parameters:
364      * template - {String} A string with tokens to be replaced.  A template
365      *     has the form "literal ${token}" where the token will be replaced
366      *     by the value of context["token"].
367      * context - {Object} An optional object with properties corresponding
368      *     to the tokens in the format string.  If no context is sent, the
369      *     window object will be used.
370      * args - {Array} Optional arguments to pass to any functions found in
371      *     the context.  If a context property is a function, the token
372      *     will be replaced by the return from the function called with
373      *     these arguments.
374      *
375      * Returns:
376      * {String} A string with tokens replaced from the context object.
377      */
378     format: function(template, context, args) {
379         if(!context) {
380             context = window;
381         }
382
383         // Example matching: 
384         // str   = ${foo.bar}
385         // match = foo.bar
386         var replacer = function(str, match) {
387             var replacement;
388
389             // Loop through all subs. Example: ${a.b.c}
390             // 0 -> replacement = context[a];
391             // 1 -> replacement = context[a][b];
392             // 2 -> replacement = context[a][b][c];
393             var subs = match.split(/\.+/);
394             for (var i=0; i< subs.length; i++) {
395                 if (i == 0) {
396                     replacement = context;
397                 }
398                 if (replacement === undefined) {
399                     break;
400                 }
401                 replacement = replacement[subs[i]];
402             }
403
404             if(typeof replacement == "function") {
405                 replacement = args ?
406                     replacement.apply(null, args) :
407                     replacement();
408             }
409
410             // If replacement is undefined, return the string 'undefined'.
411             // This is a workaround for a bugs in browsers not properly 
412             // dealing with non-participating groups in regular expressions:
413             // http://blog.stevenlevithan.com/archives/npcg-javascript
414             if (typeof replacement == 'undefined') {
415                 return 'undefined';
416             } else {
417                 return replacement; 
418             }
419         };
420
421         return template.replace(OpenLayers.String.tokenRegEx, replacer);
422     },
423
424     /**
425      * Property: tokenRegEx
426      * Used to find tokens in a string.
427      * Examples: ${a}, ${a.b.c}, ${a-b}, ${5}
428      */
429     tokenRegEx:  /\$\{([\w.]+?)\}/g,
430     
431     /**
432      * Property: numberRegEx
433      * Used to test strings as numbers.
434      */
435     numberRegEx: /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/,
436     
437     /**
438      * APIFunction: isNumeric
439      * Determine whether a string contains only a numeric value.
440      *
441      * Examples:
442      * (code)
443      * OpenLayers.String.isNumeric("6.02e23") // true
444      * OpenLayers.String.isNumeric("12 dozen") // false
445      * OpenLayers.String.isNumeric("4") // true
446      * OpenLayers.String.isNumeric(" 4 ") // false
447      * (end)
448      *
449      * Returns:
450      * {Boolean} String contains only a number.
451      */
452     isNumeric: function(value) {
453         return OpenLayers.String.numberRegEx.test(value);
454     },
455     
456     /**
457      * APIFunction: numericIf
458      * Converts a string that appears to be a numeric value into a number.
459      * 
460      * Parameters:
461      * value - {String}
462      * trimWhitespace - {Boolean}
463      *
464      * Returns:
465      * {Number|String} a Number if the passed value is a number, a String
466      *     otherwise. 
467      */
468     numericIf: function(value, trimWhitespace) {
469         var originalValue = value;
470         if (trimWhitespace === true && value != null && value.replace) {
471             value = value.replace(/^\s*|\s*$/g, "");
472         }
473         return OpenLayers.String.isNumeric(value) ? parseFloat(value) : originalValue;
474     }
475
476 };
477
478 /**
479  * Namespace: OpenLayers.Number
480  * Contains convenience functions for manipulating numbers.
481  */
482 OpenLayers.Number = {
483
484     /**
485      * Property: decimalSeparator
486      * Decimal separator to use when formatting numbers.
487      */
488     decimalSeparator: ".",
489     
490     /**
491      * Property: thousandsSeparator
492      * Thousands separator to use when formatting numbers.
493      */
494     thousandsSeparator: ",",
495     
496     /**
497      * APIFunction: limitSigDigs
498      * Limit the number of significant digits on a float.
499      * 
500      * Parameters:
501      * num - {Float}
502      * sig - {Integer}
503      * 
504      * Returns:
505      * {Float} The number, rounded to the specified number of significant
506      *     digits.
507      */
508     limitSigDigs: function(num, sig) {
509         var fig = 0;
510         if (sig > 0) {
511             fig = parseFloat(num.toPrecision(sig));
512         }
513         return fig;
514     },
515     
516     /**
517      * APIFunction: format
518      * Formats a number for output.
519      * 
520      * Parameters:
521      * num  - {Float}
522      * dec  - {Integer} Number of decimal places to round to.
523      *        Defaults to 0. Set to null to leave decimal places unchanged.
524      * tsep - {String} Thousands separator.
525      *        Default is ",".
526      * dsep - {String} Decimal separator.
527      *        Default is ".".
528      *
529      * Returns:
530      * {String} A string representing the formatted number.
531      */
532     format: function(num, dec, tsep, dsep) {
533         dec = (typeof dec != "undefined") ? dec : 0; 
534         tsep = (typeof tsep != "undefined") ? tsep :
535             OpenLayers.Number.thousandsSeparator; 
536         dsep = (typeof dsep != "undefined") ? dsep :
537             OpenLayers.Number.decimalSeparator;
538
539         if (dec != null) {
540             num = parseFloat(num.toFixed(dec));
541         }
542
543         var parts = num.toString().split(".");
544         if (parts.length == 1 && dec == null) {
545             // integer where we do not want to touch the decimals
546             dec = 0;
547         }
548         
549         var integer = parts[0];
550         if (tsep) {
551             var thousands = /(-?[0-9]+)([0-9]{3})/; 
552             while(thousands.test(integer)) { 
553                 integer = integer.replace(thousands, "$1" + tsep + "$2"); 
554             }
555         }
556         
557         var str;
558         if (dec == 0) {
559             str = integer;
560         } else {
561             var rem = parts.length > 1 ? parts[1] : "0";
562             if (dec != null) {
563                 rem = rem + new Array(dec - rem.length + 1).join("0");
564             }
565             str = integer + dsep + rem;
566         }
567         return str;
568     },
569
570     /**
571      * Method: zeroPad
572      * Create a zero padded string optionally with a radix for casting numbers.
573      *
574      * Parameters:
575      * num - {Number} The number to be zero padded.
576      * len - {Number} The length of the string to be returned.
577      * radix - {Number} An integer between 2 and 36 specifying the base to use
578      *     for representing numeric values.
579      */
580     zeroPad: function(num, len, radix) {
581         var str = num.toString(radix || 10);
582         while (str.length < len) {
583             str = "0" + str;
584         }
585         return str;
586     }    
587 };
588
589 /**
590  * Namespace: OpenLayers.Function
591  * Contains convenience functions for function manipulation.
592  */
593 OpenLayers.Function = {
594     /**
595      * APIFunction: bind
596      * Bind a function to an object.  Method to easily create closures with
597      *     'this' altered.
598      * 
599      * Parameters:
600      * func - {Function} Input function.
601      * object - {Object} The object to bind to the input function (as this).
602      * 
603      * Returns:
604      * {Function} A closure with 'this' set to the passed in object.
605      */
606     bind: function(func, object) {
607         // create a reference to all arguments past the second one
608         var args = Array.prototype.slice.call(arguments, 2);
609         return function() {
610             // Push on any additional arguments from the actual function call.
611             // These will come after those sent to the bind call.
612             var newArgs = args.concat(
613                 Array.prototype.slice.call(arguments, 0)
614             );
615             return func.apply(object, newArgs);
616         };
617     },
618     
619     /**
620      * APIFunction: bindAsEventListener
621      * Bind a function to an object, and configure it to receive the event
622      *     object as first parameter when called. 
623      * 
624      * Parameters:
625      * func - {Function} Input function to serve as an event listener.
626      * object - {Object} A reference to this.
627      * 
628      * Returns:
629      * {Function}
630      */
631     bindAsEventListener: function(func, object) {
632         return function(event) {
633             return func.call(object, event || window.event);
634         };
635     },
636     
637     /**
638      * APIFunction: False
639      * A simple function to that just does "return false". We use this to 
640      * avoid attaching anonymous functions to DOM event handlers, which 
641      * causes "issues" on IE<8.
642      * 
643      * Usage:
644      * document.onclick = OpenLayers.Function.False;
645      * 
646      * Returns:
647      * {Boolean}
648      */
649     False : function() {
650         return false;
651     },
652
653     /**
654      * APIFunction: True
655      * A simple function to that just does "return true". We use this to 
656      * avoid attaching anonymous functions to DOM event handlers, which 
657      * causes "issues" on IE<8.
658      * 
659      * Usage:
660      * document.onclick = OpenLayers.Function.True;
661      * 
662      * Returns:
663      * {Boolean}
664      */
665     True : function() {
666         return true;
667     },
668     
669     /**
670      * APIFunction: Void
671      * A reusable function that returns ``undefined``.
672      *
673      * Returns:
674      * {undefined}
675      */
676     Void: function() {}
677
678 };
679
680 /**
681  * Namespace: OpenLayers.Array
682  * Contains convenience functions for array manipulation.
683  */
684 OpenLayers.Array = {
685
686     /**
687      * APIMethod: filter
688      * Filter an array.  Provides the functionality of the
689      *     Array.prototype.filter extension to the ECMA-262 standard.  Where
690      *     available, Array.prototype.filter will be used.
691      *
692      * Based on well known example from http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/filter
693      *
694      * Parameters:
695      * array - {Array} The array to be filtered.  This array is not mutated.
696      *     Elements added to this array by the callback will not be visited.
697      * callback - {Function} A function that is called for each element in
698      *     the array.  If this function returns true, the element will be
699      *     included in the return.  The function will be called with three
700      *     arguments: the element in the array, the index of that element, and
701      *     the array itself.  If the optional caller parameter is specified
702      *     the callback will be called with this set to caller.
703      * caller - {Object} Optional object to be set as this when the callback
704      *     is called.
705      *
706      * Returns:
707      * {Array} An array of elements from the passed in array for which the
708      *     callback returns true.
709      */
710     filter: function(array, callback, caller) {
711         var selected = [];
712         if (Array.prototype.filter) {
713             selected = array.filter(callback, caller);
714         } else {
715             var len = array.length;
716             if (typeof callback != "function") {
717                 throw new TypeError();
718             }
719             for(var i=0; i<len; i++) {
720                 if (i in array) {
721                     var val = array[i];
722                     if (callback.call(caller, val, i, array)) {
723                         selected.push(val);
724                     }
725                 }
726             }        
727         }
728         return selected;
729     }
730     
731 };
732 /* ======================================================================
733     OpenLayers/BaseTypes/Bounds.js
734    ====================================================================== */
735
736 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
737  * full list of contributors). Published under the 2-clause BSD license.
738  * See license.txt in the OpenLayers distribution or repository for the
739  * full text of the license. */
740
741 /**
742  * @requires OpenLayers/BaseTypes/Class.js
743  */
744
745 /**
746  * Class: OpenLayers.Bounds
747  * Instances of this class represent bounding boxes.  Data stored as left,
748  * bottom, right, top floats. All values are initialized to null, however,
749  * you should make sure you set them before using the bounds for anything.
750  * 
751  * Possible use case:
752  * (code)
753  *     bounds = new OpenLayers.Bounds();
754  *     bounds.extend(new OpenLayers.LonLat(4,5));
755  *     bounds.extend(new OpenLayers.LonLat(5,6));
756  *     bounds.toBBOX(); // returns 4,5,5,6
757  * (end)
758  */
759 OpenLayers.Bounds = OpenLayers.Class({
760
761     /**
762      * Property: left
763      * {Number} Minimum horizontal coordinate.
764      */
765     left: null,
766
767     /**
768      * Property: bottom
769      * {Number} Minimum vertical coordinate.
770      */
771     bottom: null,
772
773     /**
774      * Property: right
775      * {Number} Maximum horizontal coordinate.
776      */
777     right: null,
778
779     /**
780      * Property: top
781      * {Number} Maximum vertical coordinate.
782      */
783     top: null,
784     
785     /**
786      * Property: centerLonLat
787      * {<OpenLayers.LonLat>} A cached center location.  This should not be
788      *     accessed directly.  Use <getCenterLonLat> instead.
789      */
790     centerLonLat: null,
791
792     /**
793      * Constructor: OpenLayers.Bounds
794      * Construct a new bounds object. Coordinates can either be passed as four
795      * arguments, or as a single argument.
796      *
797      * Parameters (four arguments):
798      * left - {Number} The left bounds of the box.  Note that for width
799      *        calculations, this is assumed to be less than the right value.
800      * bottom - {Number} The bottom bounds of the box.  Note that for height
801      *          calculations, this is assumed to be less than the top value.
802      * right - {Number} The right bounds.
803      * top - {Number} The top bounds.
804      *
805      * Parameters (single argument):
806      * bounds - {Array(Number)} [left, bottom, right, top]
807      */
808     initialize: function(left, bottom, right, top) {
809         if (OpenLayers.Util.isArray(left)) {
810             top = left[3];
811             right = left[2];
812             bottom = left[1];
813             left = left[0];
814         }
815         if (left != null) {
816             this.left = OpenLayers.Util.toFloat(left);
817         }
818         if (bottom != null) {
819             this.bottom = OpenLayers.Util.toFloat(bottom);
820         }
821         if (right != null) {
822             this.right = OpenLayers.Util.toFloat(right);
823         }
824         if (top != null) {
825             this.top = OpenLayers.Util.toFloat(top);
826         }
827     },
828
829     /**
830      * Method: clone
831      * Create a cloned instance of this bounds.
832      *
833      * Returns:
834      * {<OpenLayers.Bounds>} A fresh copy of the bounds
835      */
836     clone:function() {
837         return new OpenLayers.Bounds(this.left, this.bottom, 
838                                      this.right, this.top);
839     },
840
841     /**
842      * Method: equals
843      * Test a two bounds for equivalence.
844      *
845      * Parameters:
846      * bounds - {<OpenLayers.Bounds>}
847      *
848      * Returns:
849      * {Boolean} The passed-in bounds object has the same left,
850      *           right, top, bottom components as this.  Note that if bounds 
851      *           passed in is null, returns false.
852      */
853     equals:function(bounds) {
854         var equals = false;
855         if (bounds != null) {
856             equals = ((this.left == bounds.left) && 
857                       (this.right == bounds.right) &&
858                       (this.top == bounds.top) && 
859                       (this.bottom == bounds.bottom));
860         }
861         return equals;
862     },
863
864     /** 
865      * APIMethod: toString
866      * Returns a string representation of the bounds object.
867      * 
868      * Returns:
869      * {String} String representation of bounds object. 
870      */
871     toString:function() {
872         return [this.left, this.bottom, this.right, this.top].join(",");
873     },
874
875     /**
876      * APIMethod: toArray
877      * Returns an array representation of the bounds object.
878      *
879      * Returns an array of left, bottom, right, top properties, or -- when the
880      *     optional parameter is true -- an array of the  bottom, left, top,
881      *     right properties.
882      *
883      * Parameters:
884      * reverseAxisOrder - {Boolean} Should we reverse the axis order?
885      *
886      * Returns:
887      * {Array} array of left, bottom, right, top
888      */
889     toArray: function(reverseAxisOrder) {
890         if (reverseAxisOrder === true) {
891             return [this.bottom, this.left, this.top, this.right];
892         } else {
893             return [this.left, this.bottom, this.right, this.top];
894         }
895     },    
896
897     /** 
898      * APIMethod: toBBOX
899      * Returns a boundingbox-string representation of the bounds object.
900      * 
901      * Parameters:
902      * decimal - {Integer} How many decimal places in the bbox coords?
903      *                     Default is 6
904      * reverseAxisOrder - {Boolean} Should we reverse the axis order?
905      * 
906      * Returns:
907      * {String} Simple String representation of bounds object.
908      *          (e.g. "5,42,10,45")
909      */
910     toBBOX:function(decimal, reverseAxisOrder) {
911         if (decimal== null) {
912             decimal = 6; 
913         }
914         var mult = Math.pow(10, decimal);
915         var xmin = Math.round(this.left * mult) / mult;
916         var ymin = Math.round(this.bottom * mult) / mult;
917         var xmax = Math.round(this.right * mult) / mult;
918         var ymax = Math.round(this.top * mult) / mult;
919         if (reverseAxisOrder === true) {
920             return ymin + "," + xmin + "," + ymax + "," + xmax;
921         } else {
922             return xmin + "," + ymin + "," + xmax + "," + ymax;
923         }
924     },
925  
926     /**
927      * APIMethod: toGeometry
928      * Create a new polygon geometry based on this bounds.
929      *
930      * Returns:
931      * {<OpenLayers.Geometry.Polygon>} A new polygon with the coordinates
932      *     of this bounds.
933      */
934     toGeometry: function() {
935         return new OpenLayers.Geometry.Polygon([
936             new OpenLayers.Geometry.LinearRing([
937                 new OpenLayers.Geometry.Point(this.left, this.bottom),
938                 new OpenLayers.Geometry.Point(this.right, this.bottom),
939                 new OpenLayers.Geometry.Point(this.right, this.top),
940                 new OpenLayers.Geometry.Point(this.left, this.top)
941             ])
942         ]);
943     },
944     
945     /**
946      * APIMethod: getWidth
947      * Returns the width of the bounds.
948      * 
949      * Returns:
950      * {Float} The width of the bounds (right minus left).
951      */
952     getWidth:function() {
953         return (this.right - this.left);
954     },
955
956     /**
957      * APIMethod: getHeight
958      * Returns the height of the bounds.
959      * 
960      * Returns:
961      * {Float} The height of the bounds (top minus bottom).
962      */
963     getHeight:function() {
964         return (this.top - this.bottom);
965     },
966
967     /**
968      * APIMethod: getSize
969      * Returns an <OpenLayers.Size> object of the bounds.
970      * 
971      * Returns:
972      * {<OpenLayers.Size>} The size of the bounds.
973      */
974     getSize:function() {
975         return new OpenLayers.Size(this.getWidth(), this.getHeight());
976     },
977
978     /**
979      * APIMethod: getCenterPixel
980      * Returns the <OpenLayers.Pixel> object which represents the center of the
981      *     bounds.
982      * 
983      * Returns:
984      * {<OpenLayers.Pixel>} The center of the bounds in pixel space.
985      */
986     getCenterPixel:function() {
987         return new OpenLayers.Pixel( (this.left + this.right) / 2,
988                                      (this.bottom + this.top) / 2);
989     },
990
991     /**
992      * APIMethod: getCenterLonLat
993      * Returns the <OpenLayers.LonLat> object which represents the center of the
994      *     bounds.
995      *
996      * Returns:
997      * {<OpenLayers.LonLat>} The center of the bounds in map space.
998      */
999     getCenterLonLat:function() {
1000         if(!this.centerLonLat) {
1001             this.centerLonLat = new OpenLayers.LonLat(
1002                 (this.left + this.right) / 2, (this.bottom + this.top) / 2
1003             );
1004         }
1005         return this.centerLonLat;
1006     },
1007
1008     /**
1009      * APIMethod: scale
1010      * Scales the bounds around a pixel or lonlat. Note that the new 
1011      *     bounds may return non-integer properties, even if a pixel
1012      *     is passed. 
1013      * 
1014      * Parameters:
1015      * ratio - {Float} 
1016      * origin - {<OpenLayers.Pixel> or <OpenLayers.LonLat>}
1017      *          Default is center.
1018      *
1019      * Returns:
1020      * {<OpenLayers.Bounds>} A new bounds that is scaled by ratio
1021      *                      from origin.
1022      */
1023     scale: function(ratio, origin){
1024         if(origin == null){
1025             origin = this.getCenterLonLat();
1026         }
1027         
1028         var origx,origy;
1029
1030         // get origin coordinates
1031         if(origin.CLASS_NAME == "OpenLayers.LonLat"){
1032             origx = origin.lon;
1033             origy = origin.lat;
1034         } else {
1035             origx = origin.x;
1036             origy = origin.y;
1037         }
1038
1039         var left = (this.left - origx) * ratio + origx;
1040         var bottom = (this.bottom - origy) * ratio + origy;
1041         var right = (this.right - origx) * ratio + origx;
1042         var top = (this.top - origy) * ratio + origy;
1043         
1044         return new OpenLayers.Bounds(left, bottom, right, top);
1045     },
1046
1047     /**
1048      * APIMethod: add
1049      * Shifts the coordinates of the bound by the given horizontal and vertical
1050      *     deltas.
1051      *
1052      * (start code)
1053      * var bounds = new OpenLayers.Bounds(0, 0, 10, 10);
1054      * bounds.toString();
1055      * // => "0,0,10,10"
1056      *
1057      * bounds.add(-1.5, 4).toString();
1058      * // => "-1.5,4,8.5,14"
1059      * (end)
1060      *
1061      * This method will throw a TypeError if it is passed null as an argument.
1062      *
1063      * Parameters:
1064      * x - {Float} horizontal delta
1065      * y - {Float} vertical delta
1066      *
1067      * Returns:
1068      * {<OpenLayers.Bounds>} A new bounds whose coordinates are the same as
1069      *     this, but shifted by the passed-in x and y values.
1070      */
1071     add:function(x, y) {
1072         if ( (x == null) || (y == null) ) {
1073             throw new TypeError('Bounds.add cannot receive null values');
1074         }
1075         return new OpenLayers.Bounds(this.left + x, this.bottom + y,
1076                                      this.right + x, this.top + y);
1077     },
1078     
1079     /**
1080      * APIMethod: extend
1081      * Extend the bounds to include the <OpenLayers.LonLat>,
1082      *     <OpenLayers.Geometry.Point> or <OpenLayers.Bounds> specified.
1083      *
1084      * Please note that this function assumes that left < right and
1085      *     bottom < top.
1086      *
1087      * Parameters:
1088      * object - {<OpenLayers.LonLat>, <OpenLayers.Geometry.Point> or
1089      *     <OpenLayers.Bounds>} The object to be included in the new bounds
1090      *     object.
1091      */
1092     extend:function(object) {
1093         if (object) {
1094             switch(object.CLASS_NAME) {
1095                 case "OpenLayers.LonLat":
1096                     this.extendXY(object.lon, object.lat);
1097                     break;
1098                 case "OpenLayers.Geometry.Point":
1099                     this.extendXY(object.x, object.y);
1100                     break;
1101
1102                 case "OpenLayers.Bounds":
1103                     // clear cached center location
1104                     this.centerLonLat = null;
1105
1106                     if ( (this.left == null) || (object.left < this.left)) {
1107                         this.left = object.left;
1108                     }
1109                     if ( (this.bottom == null) || (object.bottom < this.bottom) ) {
1110                         this.bottom = object.bottom;
1111                     }
1112                     if ( (this.right == null) || (object.right > this.right) ) {
1113                         this.right = object.right;
1114                     }
1115                     if ( (this.top == null) || (object.top > this.top) ) {
1116                         this.top = object.top;
1117                     }
1118                     break;
1119             }
1120         }
1121     },
1122
1123     /**
1124      * APIMethod: extendXY
1125      * Extend the bounds to include the XY coordinate specified.
1126      *
1127      * Parameters:
1128      * x - {number} The X part of the the coordinate.
1129      * y - {number} The Y part of the the coordinate.
1130      */
1131     extendXY:function(x, y) {
1132         // clear cached center location
1133         this.centerLonLat = null;
1134
1135         if ((this.left == null) || (x < this.left)) {
1136             this.left = x;
1137         }
1138         if ((this.bottom == null) || (y < this.bottom)) {
1139             this.bottom = y;
1140         }
1141         if ((this.right == null) || (x > this.right)) {
1142             this.right = x;
1143         }
1144         if ((this.top == null) || (y > this.top)) {
1145             this.top = y;
1146         }
1147     },
1148
1149     /**
1150      * APIMethod: containsLonLat
1151      * Returns whether the bounds object contains the given <OpenLayers.LonLat>.
1152      * 
1153      * Parameters:
1154      * ll - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
1155      *     object with a 'lon' and 'lat' properties.
1156      * options - {Object} Optional parameters
1157      *
1158      * Acceptable options:
1159      * inclusive - {Boolean} Whether or not to include the border.
1160      *     Default is true.
1161      * worldBounds - {<OpenLayers.Bounds>} If a worldBounds is provided, the
1162      *     ll will be considered as contained if it exceeds the world bounds,
1163      *     but can be wrapped around the dateline so it is contained by this
1164      *     bounds.
1165      *
1166      * Returns:
1167      * {Boolean} The passed-in lonlat is within this bounds.
1168      */
1169     containsLonLat: function(ll, options) {
1170         if (typeof options === "boolean") {
1171             options =  {inclusive: options};
1172         }
1173         options = options || {};
1174         var contains = this.contains(ll.lon, ll.lat, options.inclusive),
1175             worldBounds = options.worldBounds;
1176         if (worldBounds && !contains) {
1177             var worldWidth = worldBounds.getWidth();
1178             var worldCenterX = (worldBounds.left + worldBounds.right) / 2;
1179             var worldsAway = Math.round((ll.lon - worldCenterX) / worldWidth);
1180             contains = this.containsLonLat({
1181                 lon: ll.lon - worldsAway * worldWidth,
1182                 lat: ll.lat
1183             }, {inclusive: options.inclusive});
1184         }
1185         return contains;
1186     },
1187
1188     /**
1189      * APIMethod: containsPixel
1190      * Returns whether the bounds object contains the given <OpenLayers.Pixel>.
1191      * 
1192      * Parameters:
1193      * px - {<OpenLayers.Pixel>}
1194      * inclusive - {Boolean} Whether or not to include the border. Default is
1195      *     true.
1196      *
1197      * Returns:
1198      * {Boolean} The passed-in pixel is within this bounds.
1199      */
1200     containsPixel:function(px, inclusive) {
1201         return this.contains(px.x, px.y, inclusive);
1202     },
1203     
1204     /**
1205      * APIMethod: contains
1206      * Returns whether the bounds object contains the given x and y.
1207      * 
1208      * Parameters:
1209      * x - {Float}
1210      * y - {Float}
1211      * inclusive - {Boolean} Whether or not to include the border. Default is
1212      *     true.
1213      *
1214      * Returns:
1215      * {Boolean} Whether or not the passed-in coordinates are within this
1216      *     bounds.
1217      */
1218     contains:function(x, y, inclusive) {
1219         //set default
1220         if (inclusive == null) {
1221             inclusive = true;
1222         }
1223
1224         if (x == null || y == null) {
1225             return false;
1226         }
1227
1228         x = OpenLayers.Util.toFloat(x);
1229         y = OpenLayers.Util.toFloat(y);
1230
1231         var contains = false;
1232         if (inclusive) {
1233             contains = ((x >= this.left) && (x <= this.right) && 
1234                         (y >= this.bottom) && (y <= this.top));
1235         } else {
1236             contains = ((x > this.left) && (x < this.right) && 
1237                         (y > this.bottom) && (y < this.top));
1238         }              
1239         return contains;
1240     },
1241
1242     /**
1243      * APIMethod: intersectsBounds
1244      * Determine whether the target bounds intersects this bounds.  Bounds are
1245      *     considered intersecting if any of their edges intersect or if one
1246      *     bounds contains the other.
1247      * 
1248      * Parameters:
1249      * bounds - {<OpenLayers.Bounds>} The target bounds.
1250      * options - {Object} Optional parameters.
1251      * 
1252      * Acceptable options:
1253      * inclusive - {Boolean} Treat coincident borders as intersecting.  Default
1254      *     is true.  If false, bounds that do not overlap but only touch at the
1255      *     border will not be considered as intersecting.
1256      * worldBounds - {<OpenLayers.Bounds>} If a worldBounds is provided, two
1257      *     bounds will be considered as intersecting if they intersect when 
1258      *     shifted to within the world bounds.  This applies only to bounds that
1259      *     cross or are completely outside the world bounds.
1260      *
1261      * Returns:
1262      * {Boolean} The passed-in bounds object intersects this bounds.
1263      */
1264     intersectsBounds:function(bounds, options) {
1265         if (typeof options === "boolean") {
1266             options =  {inclusive: options};
1267         }
1268         options = options || {};
1269         if (options.worldBounds) {
1270             var self = this.wrapDateLine(options.worldBounds);
1271             bounds = bounds.wrapDateLine(options.worldBounds);
1272         } else {
1273             self = this;
1274         }
1275         if (options.inclusive == null) {
1276             options.inclusive = true;
1277         }
1278         var intersects = false;
1279         var mightTouch = (
1280             self.left == bounds.right ||
1281             self.right == bounds.left ||
1282             self.top == bounds.bottom ||
1283             self.bottom == bounds.top
1284         );
1285         
1286         // if the two bounds only touch at an edge, and inclusive is false,
1287         // then the bounds don't *really* intersect.
1288         if (options.inclusive || !mightTouch) {
1289             // otherwise, if one of the boundaries even partially contains another,
1290             // inclusive of the edges, then they do intersect.
1291             var inBottom = (
1292                 ((bounds.bottom >= self.bottom) && (bounds.bottom <= self.top)) ||
1293                 ((self.bottom >= bounds.bottom) && (self.bottom <= bounds.top))
1294             );
1295             var inTop = (
1296                 ((bounds.top >= self.bottom) && (bounds.top <= self.top)) ||
1297                 ((self.top > bounds.bottom) && (self.top < bounds.top))
1298             );
1299             var inLeft = (
1300                 ((bounds.left >= self.left) && (bounds.left <= self.right)) ||
1301                 ((self.left >= bounds.left) && (self.left <= bounds.right))
1302             );
1303             var inRight = (
1304                 ((bounds.right >= self.left) && (bounds.right <= self.right)) ||
1305                 ((self.right >= bounds.left) && (self.right <= bounds.right))
1306             );
1307             intersects = ((inBottom || inTop) && (inLeft || inRight));
1308         }
1309         // document me
1310         if (options.worldBounds && !intersects) {
1311             var world = options.worldBounds;
1312             var width = world.getWidth();
1313             var selfCrosses = !world.containsBounds(self);
1314             var boundsCrosses = !world.containsBounds(bounds);
1315             if (selfCrosses && !boundsCrosses) {
1316                 bounds = bounds.add(-width, 0);
1317                 intersects = self.intersectsBounds(bounds, {inclusive: options.inclusive});
1318             } else if (boundsCrosses && !selfCrosses) {
1319                 self = self.add(-width, 0);
1320                 intersects = bounds.intersectsBounds(self, {inclusive: options.inclusive});                
1321             }
1322         }
1323         return intersects;
1324     },
1325     
1326     /**
1327      * APIMethod: containsBounds
1328      * Returns whether the bounds object contains the given <OpenLayers.Bounds>.
1329      * 
1330      * bounds - {<OpenLayers.Bounds>} The target bounds.
1331      * partial - {Boolean} If any of the target corners is within this bounds
1332      *     consider the bounds contained.  Default is false.  If false, the
1333      *     entire target bounds must be contained within this bounds.
1334      * inclusive - {Boolean} Treat shared edges as contained.  Default is
1335      *     true.
1336      *
1337      * Returns:
1338      * {Boolean} The passed-in bounds object is contained within this bounds. 
1339      */
1340     containsBounds:function(bounds, partial, inclusive) {
1341         if (partial == null) {
1342             partial = false;
1343         }
1344         if (inclusive == null) {
1345             inclusive = true;
1346         }
1347         var bottomLeft  = this.contains(bounds.left, bounds.bottom, inclusive);
1348         var bottomRight = this.contains(bounds.right, bounds.bottom, inclusive);
1349         var topLeft  = this.contains(bounds.left, bounds.top, inclusive);
1350         var topRight = this.contains(bounds.right, bounds.top, inclusive);
1351         
1352         return (partial) ? (bottomLeft || bottomRight || topLeft || topRight)
1353                          : (bottomLeft && bottomRight && topLeft && topRight);
1354     },
1355
1356     /** 
1357      * APIMethod: determineQuadrant
1358      * Returns the the quadrant ("br", "tr", "tl", "bl") in which the given
1359      *     <OpenLayers.LonLat> lies.
1360      *
1361      * Parameters:
1362      * lonlat - {<OpenLayers.LonLat>}
1363      *
1364      * Returns:
1365      * {String} The quadrant ("br" "tr" "tl" "bl") of the bounds in which the
1366      *     coordinate lies.
1367      */
1368     determineQuadrant: function(lonlat) {
1369     
1370         var quadrant = "";
1371         var center = this.getCenterLonLat();
1372         
1373         quadrant += (lonlat.lat < center.lat) ? "b" : "t";
1374         quadrant += (lonlat.lon < center.lon) ? "l" : "r";
1375     
1376         return quadrant; 
1377     },
1378     
1379     /**
1380      * APIMethod: transform
1381      * Transform the Bounds object from source to dest. 
1382      *
1383      * Parameters: 
1384      * source - {<OpenLayers.Projection>} Source projection. 
1385      * dest   - {<OpenLayers.Projection>} Destination projection. 
1386      *
1387      * Returns:
1388      * {<OpenLayers.Bounds>} Itself, for use in chaining operations.
1389      */
1390     transform: function(source, dest) {
1391         // clear cached center location
1392         this.centerLonLat = null;
1393         var ll = OpenLayers.Projection.transform(
1394             {'x': this.left, 'y': this.bottom}, source, dest);
1395         var lr = OpenLayers.Projection.transform(
1396             {'x': this.right, 'y': this.bottom}, source, dest);
1397         var ul = OpenLayers.Projection.transform(
1398             {'x': this.left, 'y': this.top}, source, dest);
1399         var ur = OpenLayers.Projection.transform(
1400             {'x': this.right, 'y': this.top}, source, dest);
1401         this.left   = Math.min(ll.x, ul.x);
1402         this.bottom = Math.min(ll.y, lr.y);
1403         this.right  = Math.max(lr.x, ur.x);
1404         this.top    = Math.max(ul.y, ur.y);
1405         return this;
1406     },
1407
1408     /**
1409      * APIMethod: wrapDateLine
1410      * Wraps the bounds object around the dateline.
1411      *  
1412      * Parameters:
1413      * maxExtent - {<OpenLayers.Bounds>}
1414      * options - {Object} Some possible options are:
1415      *
1416      * Allowed Options:
1417      *                    leftTolerance - {float} Allow for a margin of error 
1418      *                                            with the 'left' value of this 
1419      *                                            bound.
1420      *                                            Default is 0.
1421      *                    rightTolerance - {float} Allow for a margin of error 
1422      *                                             with the 'right' value of 
1423      *                                             this bound.
1424      *                                             Default is 0.
1425      * 
1426      * Returns:
1427      * {<OpenLayers.Bounds>} A copy of this bounds, but wrapped around the 
1428      *                       "dateline" (as specified by the borders of 
1429      *                       maxExtent). Note that this function only returns 
1430      *                       a different bounds value if this bounds is 
1431      *                       *entirely* outside of the maxExtent. If this 
1432      *                       bounds straddles the dateline (is part in/part 
1433      *                       out of maxExtent), the returned bounds will always 
1434      *                       cross the left edge of the given maxExtent.
1435      *.
1436      */
1437     wrapDateLine: function(maxExtent, options) {    
1438         options = options || {};
1439         
1440         var leftTolerance = options.leftTolerance || 0;
1441         var rightTolerance = options.rightTolerance || 0;
1442
1443         var newBounds = this.clone();
1444     
1445         if (maxExtent) {
1446             var width = maxExtent.getWidth();
1447
1448             //shift right?
1449             while (newBounds.left < maxExtent.left && 
1450                    newBounds.right - rightTolerance <= maxExtent.left ) { 
1451                 newBounds = newBounds.add(width, 0);
1452             }
1453
1454             //shift left?
1455             while (newBounds.left + leftTolerance >= maxExtent.right && 
1456                    newBounds.right > maxExtent.right ) { 
1457                 newBounds = newBounds.add(-width, 0);
1458             }
1459            
1460             // crosses right only? force left
1461             var newLeft = newBounds.left + leftTolerance;
1462             if (newLeft < maxExtent.right && newLeft > maxExtent.left && 
1463                    newBounds.right - rightTolerance > maxExtent.right) {
1464                 newBounds = newBounds.add(-width, 0);
1465             }
1466         }
1467                 
1468         return newBounds;
1469     },
1470
1471     CLASS_NAME: "OpenLayers.Bounds"
1472 });
1473
1474 /** 
1475  * APIFunction: fromString
1476  * Alternative constructor that builds a new OpenLayers.Bounds from a 
1477  *     parameter string.
1478  *
1479  * (begin code)
1480  * OpenLayers.Bounds.fromString("5,42,10,45");
1481  * // => equivalent to ...
1482  * new OpenLayers.Bounds(5, 42, 10, 45);
1483  * (end)
1484  *
1485  * Parameters: 
1486  * str - {String} Comma-separated bounds string. (e.g. "5,42,10,45")
1487  * reverseAxisOrder - {Boolean} Does the string use reverse axis order?
1488  *
1489  * Returns:
1490  * {<OpenLayers.Bounds>} New bounds object built from the 
1491  *                       passed-in String.
1492  */
1493 OpenLayers.Bounds.fromString = function(str, reverseAxisOrder) {
1494     var bounds = str.split(",");
1495     return OpenLayers.Bounds.fromArray(bounds, reverseAxisOrder);
1496 };
1497
1498 /** 
1499  * APIFunction: fromArray
1500  * Alternative constructor that builds a new OpenLayers.Bounds from an array.
1501  *
1502  * (begin code)
1503  * OpenLayers.Bounds.fromArray( [5, 42, 10, 45] );
1504  * // => equivalent to ...
1505  * new OpenLayers.Bounds(5, 42, 10, 45);
1506  * (end)
1507  *
1508  * Parameters:
1509  * bbox - {Array(Float)} Array of bounds values (e.g. [5,42,10,45])
1510  * reverseAxisOrder - {Boolean} Does the array use reverse axis order?
1511  *
1512  * Returns:
1513  * {<OpenLayers.Bounds>} New bounds object built from the passed-in Array.
1514  */
1515 OpenLayers.Bounds.fromArray = function(bbox, reverseAxisOrder) {
1516     return reverseAxisOrder === true ?
1517            new OpenLayers.Bounds(bbox[1], bbox[0], bbox[3], bbox[2]) :
1518            new OpenLayers.Bounds(bbox[0], bbox[1], bbox[2], bbox[3]);
1519 };
1520
1521 /** 
1522  * APIFunction: fromSize
1523  * Alternative constructor that builds a new OpenLayers.Bounds from a size.
1524  *
1525  * (begin code)
1526  * OpenLayers.Bounds.fromSize( new OpenLayers.Size(10, 20) );
1527  * // => equivalent to ...
1528  * new OpenLayers.Bounds(0, 20, 10, 0);
1529  * (end)
1530  *
1531  * Parameters:
1532  * size - {<OpenLayers.Size> or Object} <OpenLayers.Size> or an object with
1533  *     both 'w' and 'h' properties.
1534  *
1535  * Returns:
1536  * {<OpenLayers.Bounds>} New bounds object built from the passed-in size.
1537  */
1538 OpenLayers.Bounds.fromSize = function(size) {
1539     return new OpenLayers.Bounds(0,
1540                                  size.h,
1541                                  size.w,
1542                                  0);
1543 };
1544
1545 /**
1546  * Function: oppositeQuadrant
1547  * Get the opposite quadrant for a given quadrant string.
1548  *
1549  * (begin code)
1550  * OpenLayers.Bounds.oppositeQuadrant( "tl" );
1551  * // => "br"
1552  *
1553  * OpenLayers.Bounds.oppositeQuadrant( "tr" );
1554  * // => "bl"
1555  * (end)
1556  *
1557  * Parameters:
1558  * quadrant - {String} two character quadrant shortstring
1559  *
1560  * Returns:
1561  * {String} The opposing quadrant ("br" "tr" "tl" "bl"). For Example, if 
1562  *          you pass in "bl" it returns "tr", if you pass in "br" it 
1563  *          returns "tl", etc.
1564  */
1565 OpenLayers.Bounds.oppositeQuadrant = function(quadrant) {
1566     var opp = "";
1567     
1568     opp += (quadrant.charAt(0) == 't') ? 'b' : 't';
1569     opp += (quadrant.charAt(1) == 'l') ? 'r' : 'l';
1570     
1571     return opp;
1572 };
1573 /* ======================================================================
1574     OpenLayers/BaseTypes/Element.js
1575    ====================================================================== */
1576
1577 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
1578  * full list of contributors). Published under the 2-clause BSD license.
1579  * See license.txt in the OpenLayers distribution or repository for the
1580  * full text of the license. */
1581
1582 /**
1583  * @requires OpenLayers/Util.js
1584  * @requires OpenLayers/BaseTypes.js
1585  */
1586
1587 /**
1588  * Namespace: OpenLayers.Element
1589  */
1590 OpenLayers.Element = {
1591
1592     /**
1593      * APIFunction: visible
1594      * 
1595      * Parameters: 
1596      * element - {DOMElement}
1597      * 
1598      * Returns:
1599      * {Boolean} Is the element visible?
1600      */
1601     visible: function(element) {
1602         return OpenLayers.Util.getElement(element).style.display != 'none';
1603     },
1604
1605     /**
1606      * APIFunction: toggle
1607      * Toggle the visibility of element(s) passed in
1608      * 
1609      * Parameters:
1610      * element - {DOMElement} Actually user can pass any number of elements
1611      */
1612     toggle: function() {
1613         for (var i=0, len=arguments.length; i<len; i++) {
1614             var element = OpenLayers.Util.getElement(arguments[i]);
1615             var display = OpenLayers.Element.visible(element) ? 'none' 
1616                                                               : '';
1617             element.style.display = display;
1618         }
1619     },
1620
1621     /**
1622      * APIFunction: remove
1623      * Remove the specified element from the DOM.
1624      * 
1625      * Parameters:
1626      * element - {DOMElement}
1627      */
1628     remove: function(element) {
1629         element = OpenLayers.Util.getElement(element);
1630         element.parentNode.removeChild(element);
1631     },
1632
1633     /**
1634      * APIFunction: getHeight
1635      *  
1636      * Parameters:
1637      * element - {DOMElement}
1638      * 
1639      * Returns:
1640      * {Integer} The offset height of the element passed in
1641      */
1642     getHeight: function(element) {
1643         element = OpenLayers.Util.getElement(element);
1644         return element.offsetHeight;
1645     },
1646
1647     /**
1648      * Function: hasClass
1649      * Tests if an element has the given CSS class name.
1650      *
1651      * Parameters:
1652      * element - {DOMElement} A DOM element node.
1653      * name - {String} The CSS class name to search for.
1654      *
1655      * Returns:
1656      * {Boolean} The element has the given class name.
1657      */
1658     hasClass: function(element, name) {
1659         var names = element.className;
1660         return (!!names && new RegExp("(^|\\s)" + name + "(\\s|$)").test(names));
1661     },
1662     
1663     /**
1664      * Function: addClass
1665      * Add a CSS class name to an element.  Safe where element already has
1666      *     the class name.
1667      *
1668      * Parameters:
1669      * element - {DOMElement} A DOM element node.
1670      * name - {String} The CSS class name to add.
1671      *
1672      * Returns:
1673      * {DOMElement} The element.
1674      */
1675     addClass: function(element, name) {
1676         if(!OpenLayers.Element.hasClass(element, name)) {
1677             element.className += (element.className ? " " : "") + name;
1678         }
1679         return element;
1680     },
1681
1682     /**
1683      * Function: removeClass
1684      * Remove a CSS class name from an element.  Safe where element does not
1685      *     have the class name.
1686      *
1687      * Parameters:
1688      * element - {DOMElement} A DOM element node.
1689      * name - {String} The CSS class name to remove.
1690      *
1691      * Returns:
1692      * {DOMElement} The element.
1693      */
1694     removeClass: function(element, name) {
1695         var names = element.className;
1696         if(names) {
1697             element.className = OpenLayers.String.trim(
1698                 names.replace(
1699                     new RegExp("(^|\\s+)" + name + "(\\s+|$)"), " "
1700                 )
1701             );
1702         }
1703         return element;
1704     },
1705
1706     /**
1707      * Function: toggleClass
1708      * Remove a CSS class name from an element if it exists.  Add the class name
1709      *     if it doesn't exist.
1710      *
1711      * Parameters:
1712      * element - {DOMElement} A DOM element node.
1713      * name - {String} The CSS class name to toggle.
1714      *
1715      * Returns:
1716      * {DOMElement} The element.
1717      */
1718     toggleClass: function(element, name) {
1719         if(OpenLayers.Element.hasClass(element, name)) {
1720             OpenLayers.Element.removeClass(element, name);
1721         } else {
1722             OpenLayers.Element.addClass(element, name);
1723         }
1724         return element;
1725     },
1726
1727     /**
1728      * APIFunction: getStyle
1729      * 
1730      * Parameters:
1731      * element - {DOMElement}
1732      * style - {?}
1733      * 
1734      * Returns:
1735      * {?}
1736      */
1737     getStyle: function(element, style) {
1738         element = OpenLayers.Util.getElement(element);
1739
1740         var value = null;
1741         if (element && element.style) {
1742             value = element.style[OpenLayers.String.camelize(style)];
1743             if (!value) {
1744                 if (document.defaultView && 
1745                     document.defaultView.getComputedStyle) {
1746                     
1747                     var css = document.defaultView.getComputedStyle(element, null);
1748                     value = css ? css.getPropertyValue(style) : null;
1749                 } else if (element.currentStyle) {
1750                     value = element.currentStyle[OpenLayers.String.camelize(style)];
1751                 }
1752             }
1753         
1754             var positions = ['left', 'top', 'right', 'bottom'];
1755             if (window.opera &&
1756                 (OpenLayers.Util.indexOf(positions,style) != -1) &&
1757                 (OpenLayers.Element.getStyle(element, 'position') == 'static')) { 
1758                 value = 'auto';
1759             }
1760         }
1761     
1762         return value == 'auto' ? null : value;
1763     }
1764
1765 };
1766 /* ======================================================================
1767     OpenLayers/BaseTypes/LonLat.js
1768    ====================================================================== */
1769
1770 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
1771  * full list of contributors). Published under the 2-clause BSD license.
1772  * See license.txt in the OpenLayers distribution or repository for the
1773  * full text of the license. */
1774
1775 /**
1776  * @requires OpenLayers/BaseTypes/Class.js
1777  */
1778
1779 /**
1780  * Class: OpenLayers.LonLat
1781  * This class represents a longitude and latitude pair
1782  */
1783 OpenLayers.LonLat = OpenLayers.Class({
1784
1785     /** 
1786      * APIProperty: lon
1787      * {Float} The x-axis coodinate in map units
1788      */
1789     lon: 0.0,
1790     
1791     /** 
1792      * APIProperty: lat
1793      * {Float} The y-axis coordinate in map units
1794      */
1795     lat: 0.0,
1796
1797     /**
1798      * Constructor: OpenLayers.LonLat
1799      * Create a new map location. Coordinates can be passed either as two
1800      * arguments, or as a single argument.
1801      *
1802      * Parameters (two arguments):
1803      * lon - {Number} The x-axis coordinate in map units.  If your map is in
1804      *     a geographic projection, this will be the Longitude.  Otherwise,
1805      *     it will be the x coordinate of the map location in your map units.
1806      * lat - {Number} The y-axis coordinate in map units.  If your map is in
1807      *     a geographic projection, this will be the Latitude.  Otherwise,
1808      *     it will be the y coordinate of the map location in your map units.
1809      *
1810      * Parameters (single argument):
1811      * location - {Array(Float)} [lon, lat]
1812      */
1813     initialize: function(lon, lat) {
1814         if (OpenLayers.Util.isArray(lon)) {
1815             lat = lon[1];
1816             lon = lon[0];
1817         }
1818         this.lon = OpenLayers.Util.toFloat(lon);
1819         this.lat = OpenLayers.Util.toFloat(lat);
1820     },
1821     
1822     /**
1823      * Method: toString
1824      * Return a readable string version of the lonlat
1825      *
1826      * Returns:
1827      * {String} String representation of OpenLayers.LonLat object. 
1828      *           (e.g. <i>"lon=5,lat=42"</i>)
1829      */
1830     toString:function() {
1831         return ("lon=" + this.lon + ",lat=" + this.lat);
1832     },
1833
1834     /** 
1835      * APIMethod: toShortString
1836      * 
1837      * Returns:
1838      * {String} Shortened String representation of OpenLayers.LonLat object. 
1839      *         (e.g. <i>"5, 42"</i>)
1840      */
1841     toShortString:function() {
1842         return (this.lon + ", " + this.lat);
1843     },
1844
1845     /** 
1846      * APIMethod: clone
1847      * 
1848      * Returns:
1849      * {<OpenLayers.LonLat>} New OpenLayers.LonLat object with the same lon 
1850      *                       and lat values
1851      */
1852     clone:function() {
1853         return new OpenLayers.LonLat(this.lon, this.lat);
1854     },
1855
1856     /** 
1857      * APIMethod: add
1858      * 
1859      * Parameters:
1860      * lon - {Float}
1861      * lat - {Float}
1862      * 
1863      * Returns:
1864      * {<OpenLayers.LonLat>} A new OpenLayers.LonLat object with the lon and 
1865      *                       lat passed-in added to this's. 
1866      */
1867     add:function(lon, lat) {
1868         if ( (lon == null) || (lat == null) ) {
1869             throw new TypeError('LonLat.add cannot receive null values');
1870         }
1871         return new OpenLayers.LonLat(this.lon + OpenLayers.Util.toFloat(lon), 
1872                                      this.lat + OpenLayers.Util.toFloat(lat));
1873     },
1874
1875     /** 
1876      * APIMethod: equals
1877      * 
1878      * Parameters:
1879      * ll - {<OpenLayers.LonLat>}
1880      * 
1881      * Returns:
1882      * {Boolean} Boolean value indicating whether the passed-in 
1883      *           <OpenLayers.LonLat> object has the same lon and lat 
1884      *           components as this.
1885      *           Note: if ll passed in is null, returns false
1886      */
1887     equals:function(ll) {
1888         var equals = false;
1889         if (ll != null) {
1890             equals = ((this.lon == ll.lon && this.lat == ll.lat) ||
1891                       (isNaN(this.lon) && isNaN(this.lat) && isNaN(ll.lon) && isNaN(ll.lat)));
1892         }
1893         return equals;
1894     },
1895
1896     /**
1897      * APIMethod: transform
1898      * Transform the LonLat object from source to dest. This transformation is
1899      *    *in place*: if you want a *new* lonlat, use .clone() first.
1900      *
1901      * Parameters: 
1902      * source - {<OpenLayers.Projection>} Source projection. 
1903      * dest   - {<OpenLayers.Projection>} Destination projection. 
1904      *
1905      * Returns:
1906      * {<OpenLayers.LonLat>} Itself, for use in chaining operations.
1907      */
1908     transform: function(source, dest) {
1909         var point = OpenLayers.Projection.transform(
1910             {'x': this.lon, 'y': this.lat}, source, dest);
1911         this.lon = point.x;
1912         this.lat = point.y;
1913         return this;
1914     },
1915     
1916     /**
1917      * APIMethod: wrapDateLine
1918      * 
1919      * Parameters:
1920      * maxExtent - {<OpenLayers.Bounds>}
1921      * 
1922      * Returns:
1923      * {<OpenLayers.LonLat>} A copy of this lonlat, but wrapped around the 
1924      *                       "dateline" (as specified by the borders of 
1925      *                       maxExtent)
1926      */
1927     wrapDateLine: function(maxExtent) {    
1928
1929         var newLonLat = this.clone();
1930     
1931         if (maxExtent) {
1932             //shift right?
1933             while (newLonLat.lon < maxExtent.left) {
1934                 newLonLat.lon +=  maxExtent.getWidth();
1935             }    
1936            
1937             //shift left?
1938             while (newLonLat.lon > maxExtent.right) {
1939                 newLonLat.lon -= maxExtent.getWidth();
1940             }    
1941         }
1942                 
1943         return newLonLat;
1944     },
1945
1946     CLASS_NAME: "OpenLayers.LonLat"
1947 });
1948
1949 /** 
1950  * Function: fromString
1951  * Alternative constructor that builds a new <OpenLayers.LonLat> from a 
1952  *     parameter string
1953  * 
1954  * Parameters:
1955  * str - {String} Comma-separated Lon,Lat coordinate string. 
1956  *                 (e.g. <i>"5,40"</i>)
1957  * 
1958  * Returns:
1959  * {<OpenLayers.LonLat>} New <OpenLayers.LonLat> object built from the 
1960  *                       passed-in String.
1961  */
1962 OpenLayers.LonLat.fromString = function(str) {
1963     var pair = str.split(",");
1964     return new OpenLayers.LonLat(pair[0], pair[1]);
1965 };
1966
1967 /** 
1968  * Function: fromArray
1969  * Alternative constructor that builds a new <OpenLayers.LonLat> from an 
1970  *     array of two numbers that represent lon- and lat-values.
1971  * 
1972  * Parameters:
1973  * arr - {Array(Float)} Array of lon/lat values (e.g. [5,-42])
1974  * 
1975  * Returns:
1976  * {<OpenLayers.LonLat>} New <OpenLayers.LonLat> object built from the 
1977  *                       passed-in array.
1978  */
1979 OpenLayers.LonLat.fromArray = function(arr) {
1980     var gotArr = OpenLayers.Util.isArray(arr),
1981         lon = gotArr && arr[0],
1982         lat = gotArr && arr[1];
1983     return new OpenLayers.LonLat(lon, lat);
1984 };
1985 /* ======================================================================
1986     OpenLayers/BaseTypes/Pixel.js
1987    ====================================================================== */
1988
1989 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
1990  * full list of contributors). Published under the 2-clause BSD license.
1991  * See license.txt in the OpenLayers distribution or repository for the
1992  * full text of the license. */
1993
1994 /**
1995  * @requires OpenLayers/BaseTypes/Class.js
1996  */
1997
1998 /**
1999  * Class: OpenLayers.Pixel
2000  * This class represents a screen coordinate, in x and y coordinates
2001  */
2002 OpenLayers.Pixel = OpenLayers.Class({
2003     
2004     /**
2005      * APIProperty: x
2006      * {Number} The x coordinate
2007      */
2008     x: 0.0,
2009
2010     /**
2011      * APIProperty: y
2012      * {Number} The y coordinate
2013      */
2014     y: 0.0,
2015     
2016     /**
2017      * Constructor: OpenLayers.Pixel
2018      * Create a new OpenLayers.Pixel instance
2019      *
2020      * Parameters:
2021      * x - {Number} The x coordinate
2022      * y - {Number} The y coordinate
2023      *
2024      * Returns:
2025      * An instance of OpenLayers.Pixel
2026      */
2027     initialize: function(x, y) {
2028         this.x = parseFloat(x);
2029         this.y = parseFloat(y);
2030     },
2031     
2032     /**
2033      * Method: toString
2034      * Cast this object into a string
2035      *
2036      * Returns:
2037      * {String} The string representation of Pixel. ex: "x=200.4,y=242.2"
2038      */
2039     toString:function() {
2040         return ("x=" + this.x + ",y=" + this.y);
2041     },
2042
2043     /**
2044      * APIMethod: clone
2045      * Return a clone of this pixel object
2046      *
2047      * Returns:
2048      * {<OpenLayers.Pixel>} A clone pixel
2049      */
2050     clone:function() {
2051         return new OpenLayers.Pixel(this.x, this.y); 
2052     },
2053     
2054     /**
2055      * APIMethod: equals
2056      * Determine whether one pixel is equivalent to another
2057      *
2058      * Parameters:
2059      * px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object with
2060      *                                  a 'x' and 'y' properties.
2061      *
2062      * Returns:
2063      * {Boolean} The point passed in as parameter is equal to this. Note that
2064      * if px passed in is null, returns false.
2065      */
2066     equals:function(px) {
2067         var equals = false;
2068         if (px != null) {
2069             equals = ((this.x == px.x && this.y == px.y) ||
2070                       (isNaN(this.x) && isNaN(this.y) && isNaN(px.x) && isNaN(px.y)));
2071         }
2072         return equals;
2073     },
2074
2075     /**
2076      * APIMethod: distanceTo
2077      * Returns the distance to the pixel point passed in as a parameter.
2078      *
2079      * Parameters:
2080      * px - {<OpenLayers.Pixel>}
2081      *
2082      * Returns:
2083      * {Float} The pixel point passed in as parameter to calculate the
2084      *     distance to.
2085      */
2086     distanceTo:function(px) {
2087         return Math.sqrt(
2088             Math.pow(this.x - px.x, 2) +
2089             Math.pow(this.y - px.y, 2)
2090         );
2091     },
2092
2093     /**
2094      * APIMethod: add
2095      *
2096      * Parameters:
2097      * x - {Integer}
2098      * y - {Integer}
2099      *
2100      * Returns:
2101      * {<OpenLayers.Pixel>} A new Pixel with this pixel's x&y augmented by the 
2102      * values passed in.
2103      */
2104     add:function(x, y) {
2105         if ( (x == null) || (y == null) ) {
2106             throw new TypeError('Pixel.add cannot receive null values');
2107         }
2108         return new OpenLayers.Pixel(this.x + x, this.y + y);
2109     },
2110
2111     /**
2112     * APIMethod: offset
2113     * 
2114     * Parameters
2115     * px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object with
2116     *                                  a 'x' and 'y' properties.
2117     * 
2118     * Returns:
2119     * {<OpenLayers.Pixel>} A new Pixel with this pixel's x&y augmented by the 
2120     *                      x&y values of the pixel passed in.
2121     */
2122     offset:function(px) {
2123         var newPx = this.clone();
2124         if (px) {
2125             newPx = this.add(px.x, px.y);
2126         }
2127         return newPx;
2128     },
2129
2130     CLASS_NAME: "OpenLayers.Pixel"
2131 });
2132 /* ======================================================================
2133     OpenLayers/BaseTypes/Size.js
2134    ====================================================================== */
2135
2136 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
2137  * full list of contributors). Published under the 2-clause BSD license.
2138  * See license.txt in the OpenLayers distribution or repository for the
2139  * full text of the license. */
2140
2141 /**
2142  * @requires OpenLayers/BaseTypes/Class.js
2143  */
2144
2145 /**
2146  * Class: OpenLayers.Size
2147  * Instances of this class represent a width/height pair
2148  */
2149 OpenLayers.Size = OpenLayers.Class({
2150
2151     /**
2152      * APIProperty: w
2153      * {Number} width
2154      */
2155     w: 0.0,
2156     
2157     /**
2158      * APIProperty: h
2159      * {Number} height
2160      */
2161     h: 0.0,
2162
2163
2164     /**
2165      * Constructor: OpenLayers.Size
2166      * Create an instance of OpenLayers.Size
2167      *
2168      * Parameters:
2169      * w - {Number} width
2170      * h - {Number} height
2171      */
2172     initialize: function(w, h) {
2173         this.w = parseFloat(w);
2174         this.h = parseFloat(h);
2175     },
2176
2177     /**
2178      * Method: toString
2179      * Return the string representation of a size object
2180      *
2181      * Returns:
2182      * {String} The string representation of OpenLayers.Size object. 
2183      * (e.g. <i>"w=55,h=66"</i>)
2184      */
2185     toString:function() {
2186         return ("w=" + this.w + ",h=" + this.h);
2187     },
2188
2189     /**
2190      * APIMethod: clone
2191      * Create a clone of this size object
2192      *
2193      * Returns:
2194      * {<OpenLayers.Size>} A new OpenLayers.Size object with the same w and h
2195      * values
2196      */
2197     clone:function() {
2198         return new OpenLayers.Size(this.w, this.h);
2199     },
2200
2201     /**
2202      *
2203      * APIMethod: equals
2204      * Determine where this size is equal to another
2205      *
2206      * Parameters:
2207      * sz - {<OpenLayers.Size>|Object} An OpenLayers.Size or an object with
2208      *                                  a 'w' and 'h' properties.
2209      *
2210      * Returns: 
2211      * {Boolean} The passed in size has the same h and w properties as this one.
2212      * Note that if sz passed in is null, returns false.
2213      */
2214     equals:function(sz) {
2215         var equals = false;
2216         if (sz != null) {
2217             equals = ((this.w == sz.w && this.h == sz.h) ||
2218                       (isNaN(this.w) && isNaN(this.h) && isNaN(sz.w) && isNaN(sz.h)));
2219         }
2220         return equals;
2221     },
2222
2223     CLASS_NAME: "OpenLayers.Size"
2224 });
2225 /* ======================================================================
2226     OpenLayers/Console.js
2227    ====================================================================== */
2228
2229 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
2230  * full list of contributors). Published under the 2-clause BSD license.
2231  * See license.txt in the OpenLayers distribution or repository for the
2232  * full text of the license. */
2233
2234 /**
2235  * @requires OpenLayers/BaseTypes/Class.js
2236  */
2237
2238 /**
2239  * Namespace: OpenLayers.Console
2240  * The OpenLayers.Console namespace is used for debugging and error logging.
2241  * If the Firebug Lite (../Firebug/firebug.js) is included before this script,
2242  * calls to OpenLayers.Console methods will get redirected to window.console.
2243  * This makes use of the Firebug extension where available and allows for
2244  * cross-browser debugging Firebug style.
2245  *
2246  * Note:
2247  * Note that behavior will differ with the Firebug extension and Firebug Lite.
2248  * Most notably, the Firebug Lite console does not currently allow for
2249  * hyperlinks to code or for clicking on object to explore their properties.
2250  * 
2251  */
2252 OpenLayers.Console = {
2253     /**
2254      * Create empty functions for all console methods.  The real value of these
2255      * properties will be set if Firebug Lite (../Firebug/firebug.js script) is
2256      * included.  We explicitly require the Firebug Lite script to trigger
2257      * functionality of the OpenLayers.Console methods.
2258      */
2259     
2260     /**
2261      * APIFunction: log
2262      * Log an object in the console.  The Firebug Lite console logs string
2263      * representation of objects.  Given multiple arguments, they will
2264      * be cast to strings and logged with a space delimiter.  If the first
2265      * argument is a string with printf-like formatting, subsequent arguments
2266      * will be used in string substitution.  Any additional arguments (beyond
2267      * the number substituted in a format string) will be appended in a space-
2268      * delimited line.
2269      * 
2270      * Parameters:
2271      * object - {Object}
2272      */
2273     log: function() {},
2274
2275     /**
2276      * APIFunction: debug
2277      * Writes a message to the console, including a hyperlink to the line
2278      * where it was called.
2279      *
2280      * May be called with multiple arguments as with OpenLayers.Console.log().
2281      * 
2282      * Parameters:
2283      * object - {Object}
2284      */
2285     debug: function() {},
2286
2287     /**
2288      * APIFunction: info
2289      * Writes a message to the console with the visual "info" icon and color
2290      * coding and a hyperlink to the line where it was called.
2291      *
2292      * May be called with multiple arguments as with OpenLayers.Console.log().
2293      * 
2294      * Parameters:
2295      * object - {Object}
2296      */
2297     info: function() {},
2298
2299     /**
2300      * APIFunction: warn
2301      * Writes a message to the console with the visual "warning" icon and
2302      * color coding and a hyperlink to the line where it was called.
2303      *
2304      * May be called with multiple arguments as with OpenLayers.Console.log().
2305      * 
2306      * Parameters:
2307      * object - {Object}
2308      */
2309     warn: function() {},
2310
2311     /**
2312      * APIFunction: error
2313      * Writes a message to the console with the visual "error" icon and color
2314      * coding and a hyperlink to the line where it was called.
2315      *
2316      * May be called with multiple arguments as with OpenLayers.Console.log().
2317      * 
2318      * Parameters:
2319      * object - {Object}
2320      */
2321     error: function() {},
2322     
2323     /**
2324      * APIFunction: userError
2325      * A single interface for showing error messages to the user. The default
2326      * behavior is a Javascript alert, though this can be overridden by
2327      * reassigning OpenLayers.Console.userError to a different function.
2328      *
2329      * Expects a single error message
2330      * 
2331      * Parameters:
2332      * error - {Object}
2333      */
2334     userError: function(error) {
2335         alert(error);
2336     },
2337
2338     /**
2339      * APIFunction: assert
2340      * Tests that an expression is true. If not, it will write a message to
2341      * the console and throw an exception.
2342      *
2343      * May be called with multiple arguments as with OpenLayers.Console.log().
2344      * 
2345      * Parameters:
2346      * object - {Object}
2347      */
2348     assert: function() {},
2349
2350     /**
2351      * APIFunction: dir
2352      * Prints an interactive listing of all properties of the object. This
2353      * looks identical to the view that you would see in the DOM tab.
2354      * 
2355      * Parameters:
2356      * object - {Object}
2357      */
2358     dir: function() {},
2359
2360     /**
2361      * APIFunction: dirxml
2362      * Prints the XML source tree of an HTML or XML element. This looks
2363      * identical to the view that you would see in the HTML tab. You can click
2364      * on any node to inspect it in the HTML tab.
2365      * 
2366      * Parameters:
2367      * object - {Object}
2368      */
2369     dirxml: function() {},
2370
2371     /**
2372      * APIFunction: trace
2373      * Prints an interactive stack trace of JavaScript execution at the point
2374      * where it is called.  The stack trace details the functions on the stack,
2375      * as well as the values that were passed as arguments to each function.
2376      * You can click each function to take you to its source in the Script tab,
2377      * and click each argument value to inspect it in the DOM or HTML tabs.
2378      * 
2379      */
2380     trace: function() {},
2381
2382     /**
2383      * APIFunction: group
2384      * Writes a message to the console and opens a nested block to indent all
2385      * future messages sent to the console. Call OpenLayers.Console.groupEnd()
2386      * to close the block.
2387      *
2388      * May be called with multiple arguments as with OpenLayers.Console.log().
2389      * 
2390      * Parameters:
2391      * object - {Object}
2392      */
2393     group: function() {},
2394
2395     /**
2396      * APIFunction: groupEnd
2397      * Closes the most recently opened block created by a call to
2398      * OpenLayers.Console.group
2399      */
2400     groupEnd: function() {},
2401     
2402     /**
2403      * APIFunction: time
2404      * Creates a new timer under the given name. Call
2405      * OpenLayers.Console.timeEnd(name)
2406      * with the same name to stop the timer and print the time elapsed.
2407      *
2408      * Parameters:
2409      * name - {String}
2410      */
2411     time: function() {},
2412
2413     /**
2414      * APIFunction: timeEnd
2415      * Stops a timer created by a call to OpenLayers.Console.time(name) and
2416      * writes the time elapsed.
2417      *
2418      * Parameters:
2419      * name - {String}
2420      */
2421     timeEnd: function() {},
2422
2423     /**
2424      * APIFunction: profile
2425      * Turns on the JavaScript profiler. The optional argument title would
2426      * contain the text to be printed in the header of the profile report.
2427      *
2428      * This function is not currently implemented in Firebug Lite.
2429      * 
2430      * Parameters:
2431      * title - {String} Optional title for the profiler
2432      */
2433     profile: function() {},
2434
2435     /**
2436      * APIFunction: profileEnd
2437      * Turns off the JavaScript profiler and prints its report.
2438      * 
2439      * This function is not currently implemented in Firebug Lite.
2440      */
2441     profileEnd: function() {},
2442
2443     /**
2444      * APIFunction: count
2445      * Writes the number of times that the line of code where count was called
2446      * was executed. The optional argument title will print a message in
2447      * addition to the number of the count.
2448      *
2449      * This function is not currently implemented in Firebug Lite.
2450      *
2451      * Parameters:
2452      * title - {String} Optional title to be printed with count
2453      */
2454     count: function() {},
2455
2456     CLASS_NAME: "OpenLayers.Console"
2457 };
2458
2459 /**
2460  * Execute an anonymous function to extend the OpenLayers.Console namespace
2461  * if the firebug.js script is included.  This closure is used so that the
2462  * "scripts" and "i" variables don't pollute the global namespace.
2463  */
2464 (function() {
2465     /**
2466      * If Firebug Lite is included (before this script), re-route all
2467      * OpenLayers.Console calls to the console object.
2468      */
2469     var scripts = document.getElementsByTagName("script");
2470     for(var i=0, len=scripts.length; i<len; ++i) {
2471         if(scripts[i].src.indexOf("firebug.js") != -1) {
2472             if(console) {
2473                 OpenLayers.Util.extend(OpenLayers.Console, console);
2474                 break;
2475             }
2476         }
2477     }
2478 })();
2479 /* ======================================================================
2480     OpenLayers/Lang.js
2481    ====================================================================== */
2482
2483 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
2484  * full list of contributors). Published under the 2-clause BSD license.
2485  * See license.txt in the OpenLayers distribution or repository for the
2486  * full text of the license. */
2487
2488 /**
2489  * @requires OpenLayers/BaseTypes.js
2490  * @requires OpenLayers/Console.js
2491  */
2492
2493 /**
2494  * Namespace: OpenLayers.Lang
2495  * Internationalization namespace.  Contains dictionaries in various languages
2496  *     and methods to set and get the current language.
2497  */
2498 OpenLayers.Lang = {
2499     
2500     /** 
2501      * Property: code
2502      * {String}  Current language code to use in OpenLayers.  Use the
2503      *     <setCode> method to set this value and the <getCode> method to
2504      *     retrieve it.
2505      */
2506     code: null,
2507
2508     /** 
2509      * APIProperty: defaultCode
2510      * {String} Default language to use when a specific language can't be
2511      *     found.  Default is "en".
2512      */
2513     defaultCode: "en",
2514         
2515     /**
2516      * APIFunction: getCode
2517      * Get the current language code.
2518      *
2519      * Returns:
2520      * {String} The current language code.
2521      */
2522     getCode: function() {
2523         if(!OpenLayers.Lang.code) {
2524             OpenLayers.Lang.setCode();
2525         }
2526         return OpenLayers.Lang.code;
2527     },
2528     
2529     /**
2530      * APIFunction: setCode
2531      * Set the language code for string translation.  This code is used by
2532      *     the <OpenLayers.Lang.translate> method.
2533      *
2534      * Parameters:
2535      * code - {String} These codes follow the IETF recommendations at
2536      *     http://www.ietf.org/rfc/rfc3066.txt.  If no value is set, the
2537      *     browser's language setting will be tested.  If no <OpenLayers.Lang>
2538      *     dictionary exists for the code, the <OpenLayers.String.defaultLang>
2539      *     will be used.
2540      */
2541     setCode: function(code) {
2542         var lang;
2543         if(!code) {
2544             code = (OpenLayers.BROWSER_NAME == "msie") ?
2545                 navigator.userLanguage : navigator.language;
2546         }
2547         var parts = code.split('-');
2548         parts[0] = parts[0].toLowerCase();
2549         if(typeof OpenLayers.Lang[parts[0]] == "object") {
2550             lang = parts[0];
2551         }
2552
2553         // check for regional extensions
2554         if(parts[1]) {
2555             var testLang = parts[0] + '-' + parts[1].toUpperCase();
2556             if(typeof OpenLayers.Lang[testLang] == "object") {
2557                 lang = testLang;
2558             }
2559         }
2560         if(!lang) {
2561             OpenLayers.Console.warn(
2562                 'Failed to find OpenLayers.Lang.' + parts.join("-") +
2563                 ' dictionary, falling back to default language'
2564             );
2565             lang = OpenLayers.Lang.defaultCode;
2566         }
2567         
2568         OpenLayers.Lang.code = lang;
2569     },
2570
2571     /**
2572      * APIMethod: translate
2573      * Looks up a key from a dictionary based on the current language string.
2574      *     The value of <getCode> will be used to determine the appropriate
2575      *     dictionary.  Dictionaries are stored in <OpenLayers.Lang>.
2576      *
2577      * Parameters:
2578      * key - {String} The key for an i18n string value in the dictionary.
2579      * context - {Object} Optional context to be used with
2580      *     <OpenLayers.String.format>.
2581      * 
2582      * Returns:
2583      * {String} A internationalized string.
2584      */
2585     translate: function(key, context) {
2586         var dictionary = OpenLayers.Lang[OpenLayers.Lang.getCode()];
2587         var message = dictionary && dictionary[key];
2588         if(!message) {
2589             // Message not found, fall back to message key
2590             message = key;
2591         }
2592         if(context) {
2593             message = OpenLayers.String.format(message, context);
2594         }
2595         return message;
2596     }
2597     
2598 };
2599
2600
2601 /**
2602  * APIMethod: OpenLayers.i18n
2603  * Alias for <OpenLayers.Lang.translate>.  Looks up a key from a dictionary
2604  *     based on the current language string. The value of
2605  *     <OpenLayers.Lang.getCode> will be used to determine the appropriate
2606  *     dictionary.  Dictionaries are stored in <OpenLayers.Lang>.
2607  *
2608  * Parameters:
2609  * key - {String} The key for an i18n string value in the dictionary.
2610  * context - {Object} Optional context to be used with
2611  *     <OpenLayers.String.format>.
2612  * 
2613  * Returns:
2614  * {String} A internationalized string.
2615  */
2616 OpenLayers.i18n = OpenLayers.Lang.translate;
2617 /* ======================================================================
2618     OpenLayers/Util.js
2619    ====================================================================== */
2620
2621 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
2622  * full list of contributors). Published under the 2-clause BSD license.
2623  * See license.txt in the OpenLayers distribution or repository for the
2624  * full text of the license. */
2625
2626 /**
2627  * @requires OpenLayers/BaseTypes.js
2628  * @requires OpenLayers/BaseTypes/Bounds.js
2629  * @requires OpenLayers/BaseTypes/Element.js
2630  * @requires OpenLayers/BaseTypes/LonLat.js
2631  * @requires OpenLayers/BaseTypes/Pixel.js
2632  * @requires OpenLayers/BaseTypes/Size.js
2633  * @requires OpenLayers/Lang.js
2634  */
2635
2636 /**
2637  * Namespace: Util
2638  */
2639 OpenLayers.Util = OpenLayers.Util || {};
2640
2641 /** 
2642  * Function: getElement
2643  * This is the old $() from prototype
2644  *
2645  * Parameters:
2646  * e - {String or DOMElement or Window}
2647  *
2648  * Returns:
2649  * {Array(DOMElement) or DOMElement}
2650  */
2651 OpenLayers.Util.getElement = function() {
2652     var elements = [];
2653
2654     for (var i=0, len=arguments.length; i<len; i++) {
2655         var element = arguments[i];
2656         if (typeof element == 'string') {
2657             element = document.getElementById(element);
2658         }
2659         if (arguments.length == 1) {
2660             return element;
2661         }
2662         elements.push(element);
2663     }
2664     return elements;
2665 };
2666
2667 /**
2668  * Function: isElement
2669  * A cross-browser implementation of "e instanceof Element".
2670  *
2671  * Parameters:
2672  * o - {Object} The object to test.
2673  *
2674  * Returns:
2675  * {Boolean}
2676  */
2677 OpenLayers.Util.isElement = function(o) {
2678     return !!(o && o.nodeType === 1);
2679 };
2680
2681 /**
2682  * Function: isArray
2683  * Tests that the provided object is an array.
2684  * This test handles the cross-IFRAME case not caught
2685  * by "a instanceof Array" and should be used instead.
2686  * 
2687  * Parameters:
2688  * a - {Object} the object test.
2689  * 
2690  * Returns:
2691  * {Boolean} true if the object is an array.
2692  */
2693 OpenLayers.Util.isArray = function(a) {
2694     return (Object.prototype.toString.call(a) === '[object Array]');
2695 };
2696
2697 /** 
2698  * Function: removeItem
2699  * Remove an object from an array. Iterates through the array
2700  *     to find the item, then removes it.
2701  *
2702  * Parameters:
2703  * array - {Array}
2704  * item - {Object}
2705  * 
2706  * Returns:
2707  * {Array} A reference to the array
2708  */
2709 OpenLayers.Util.removeItem = function(array, item) {
2710     for(var i = array.length - 1; i >= 0; i--) {
2711         if(array[i] == item) {
2712             array.splice(i,1);
2713             //break;more than once??
2714         }
2715     }
2716     return array;
2717 };
2718
2719 /** 
2720  * Function: indexOf
2721  * Seems to exist already in FF, but not in MOZ.
2722  * 
2723  * Parameters:
2724  * array - {Array}
2725  * obj - {*}
2726  * 
2727  * Returns:
2728  * {Integer} The index at which the first object was found in the array.
2729  *           If not found, returns -1.
2730  */
2731 OpenLayers.Util.indexOf = function(array, obj) {
2732     // use the build-in function if available.
2733     if (typeof array.indexOf == "function") {
2734         return array.indexOf(obj);
2735     } else {
2736         for (var i = 0, len = array.length; i < len; i++) {
2737             if (array[i] == obj) {
2738                 return i;
2739             }
2740         }
2741         return -1;   
2742     }
2743 };
2744
2745
2746 /**
2747  * Property: dotless
2748  * {RegExp}
2749  * Compiled regular expression to match dots (".").  This is used for replacing
2750  *     dots in identifiers.  Because object identifiers are frequently used for
2751  *     DOM element identifiers by the library, we avoid using dots to make for
2752  *     more sensible CSS selectors.
2753  *
2754  * TODO: Use a module pattern to avoid bloating the API with stuff like this.
2755  */
2756 OpenLayers.Util.dotless = /\./g;
2757
2758 /**
2759  * Function: modifyDOMElement
2760  * 
2761  * Modifies many properties of a DOM element all at once.  Passing in 
2762  * null to an individual parameter will avoid setting the attribute.
2763  *
2764  * Parameters:
2765  * element - {DOMElement} DOM element to modify.
2766  * id - {String} The element id attribute to set.  Note that dots (".") will be
2767  *     replaced with underscore ("_") in setting the element id.
2768  * px - {<OpenLayers.Pixel>|Object} The element left and top position,
2769  *                                  OpenLayers.Pixel or an object with
2770  *                                  a 'x' and 'y' properties.
2771  * sz - {<OpenLayers.Size>|Object} The element width and height,
2772  *                                 OpenLayers.Size or an object with a
2773  *                                 'w' and 'h' properties.
2774  * position - {String}       The position attribute.  eg: absolute, 
2775  *                           relative, etc.
2776  * border - {String}         The style.border attribute.  eg:
2777  *                           solid black 2px
2778  * overflow - {String}       The style.overview attribute.  
2779  * opacity - {Float}         Fractional value (0.0 - 1.0)
2780  */
2781 OpenLayers.Util.modifyDOMElement = function(element, id, px, sz, position, 
2782                                             border, overflow, opacity) {
2783
2784     if (id) {
2785         element.id = id.replace(OpenLayers.Util.dotless, "_");
2786     }
2787     if (px) {
2788         element.style.left = px.x + "px";
2789         element.style.top = px.y + "px";
2790     }
2791     if (sz) {
2792         element.style.width = sz.w + "px";
2793         element.style.height = sz.h + "px";
2794     }
2795     if (position) {
2796         element.style.position = position;
2797     }
2798     if (border) {
2799         element.style.border = border;
2800     }
2801     if (overflow) {
2802         element.style.overflow = overflow;
2803     }
2804     if (parseFloat(opacity) >= 0.0 && parseFloat(opacity) < 1.0) {
2805         element.style.filter = 'alpha(opacity=' + (opacity * 100) + ')';
2806         element.style.opacity = opacity;
2807     } else if (parseFloat(opacity) == 1.0) {
2808         element.style.filter = '';
2809         element.style.opacity = '';
2810     }
2811 };
2812
2813 /** 
2814  * Function: createDiv
2815  * Creates a new div and optionally set some standard attributes.
2816  * Null may be passed to each parameter if you do not wish to
2817  * set a particular attribute.
2818  * Note - zIndex is NOT set on the resulting div.
2819  * 
2820  * Parameters:
2821  * id - {String} An identifier for this element.  If no id is
2822  *               passed an identifier will be created 
2823  *               automatically.  Note that dots (".") will be replaced with
2824  *               underscore ("_") when generating ids.
2825  * px - {<OpenLayers.Pixel>|Object} The element left and top position,
2826  *                                  OpenLayers.Pixel or an object with
2827  *                                  a 'x' and 'y' properties.
2828  * sz - {<OpenLayers.Size>|Object} The element width and height,
2829  *                                 OpenLayers.Size or an object with a
2830  *                                 'w' and 'h' properties.
2831  * imgURL - {String} A url pointing to an image to use as a 
2832  *                   background image.
2833  * position - {String} The style.position value. eg: absolute,
2834  *                     relative etc.
2835  * border - {String} The the style.border value. 
2836  *                   eg: 2px solid black
2837  * overflow - {String} The style.overflow value. Eg. hidden
2838  * opacity - {Float} Fractional value (0.0 - 1.0)
2839  * 
2840  * Returns: 
2841  * {DOMElement} A DOM Div created with the specified attributes.
2842  */
2843 OpenLayers.Util.createDiv = function(id, px, sz, imgURL, position, 
2844                                      border, overflow, opacity) {
2845
2846     var dom = document.createElement('div');
2847
2848     if (imgURL) {
2849         dom.style.backgroundImage = 'url(' + imgURL + ')';
2850     }
2851
2852     //set generic properties
2853     if (!id) {
2854         id = OpenLayers.Util.createUniqueID("OpenLayersDiv");
2855     }
2856     if (!position) {
2857         position = "absolute";
2858     }
2859     OpenLayers.Util.modifyDOMElement(dom, id, px, sz, position, 
2860                                      border, overflow, opacity);
2861
2862     return dom;
2863 };
2864
2865 /**
2866  * Function: createImage
2867  * Creates an img element with specific attribute values.
2868  *  
2869  * Parameters:
2870  * id - {String} The id field for the img.  If none assigned one will be
2871  *               automatically generated.
2872  * px - {<OpenLayers.Pixel>|Object} The element left and top position,
2873  *                                  OpenLayers.Pixel or an object with
2874  *                                  a 'x' and 'y' properties.
2875  * sz - {<OpenLayers.Size>|Object} The element width and height,
2876  *                                 OpenLayers.Size or an object with a
2877  *                                 'w' and 'h' properties.
2878  * imgURL - {String} The url to use as the image source.
2879  * position - {String} The style.position value.
2880  * border - {String} The border to place around the image.
2881  * opacity - {Float} Fractional value (0.0 - 1.0)
2882  * delayDisplay - {Boolean} If true waits until the image has been
2883  *                          loaded.
2884  * 
2885  * Returns:
2886  * {DOMElement} A DOM Image created with the specified attributes.
2887  */
2888 OpenLayers.Util.createImage = function(id, px, sz, imgURL, position, border,
2889                                        opacity, delayDisplay) {
2890
2891     var image = document.createElement("img");
2892
2893     //set generic properties
2894     if (!id) {
2895         id = OpenLayers.Util.createUniqueID("OpenLayersDiv");
2896     }
2897     if (!position) {
2898         position = "relative";
2899     }
2900     OpenLayers.Util.modifyDOMElement(image, id, px, sz, position, 
2901                                      border, null, opacity);
2902
2903     if (delayDisplay) {
2904         image.style.display = "none";
2905         function display() {
2906             image.style.display = "";
2907             OpenLayers.Event.stopObservingElement(image);
2908         }
2909         OpenLayers.Event.observe(image, "load", display);
2910         OpenLayers.Event.observe(image, "error", display);
2911     }
2912     
2913     //set special properties
2914     image.style.alt = id;
2915     image.galleryImg = "no";
2916     if (imgURL) {
2917         image.src = imgURL;
2918     }
2919         
2920     return image;
2921 };
2922
2923 /**
2924  * Property: IMAGE_RELOAD_ATTEMPTS
2925  * {Integer} How many times should we try to reload an image before giving up?
2926  *           Default is 0
2927  */
2928 OpenLayers.IMAGE_RELOAD_ATTEMPTS = 0;
2929
2930 /**
2931  * Property: alphaHackNeeded
2932  * {Boolean} true if the png alpha hack is necessary and possible, false otherwise.
2933  */
2934 OpenLayers.Util.alphaHackNeeded = null;
2935
2936 /**
2937  * Function: alphaHack
2938  * Checks whether it's necessary (and possible) to use the png alpha
2939  * hack which allows alpha transparency for png images under Internet
2940  * Explorer.
2941  * 
2942  * Returns:
2943  * {Boolean} true if the png alpha hack is necessary and possible, false otherwise.
2944  */
2945 OpenLayers.Util.alphaHack = function() {
2946     if (OpenLayers.Util.alphaHackNeeded == null) {
2947         var arVersion = navigator.appVersion.split("MSIE");
2948         var version = parseFloat(arVersion[1]);
2949         var filter = false;
2950     
2951         // IEs4Lin dies when trying to access document.body.filters, because 
2952         // the property is there, but requires a DLL that can't be provided. This
2953         // means that we need to wrap this in a try/catch so that this can
2954         // continue.
2955     
2956         try { 
2957             filter = !!(document.body.filters);
2958         } catch (e) {}    
2959     
2960         OpenLayers.Util.alphaHackNeeded = (filter && 
2961                                            (version >= 5.5) && (version < 7));
2962     }
2963     return OpenLayers.Util.alphaHackNeeded;
2964 };
2965
2966 /** 
2967  * Function: modifyAlphaImageDiv
2968  * 
2969  * Parameters:
2970  * div - {DOMElement} Div containing Alpha-adjusted Image
2971  * id - {String}
2972  * px - {<OpenLayers.Pixel>|Object} OpenLayers.Pixel or an object with
2973  *                                  a 'x' and 'y' properties.
2974  * sz - {<OpenLayers.Size>|Object} OpenLayers.Size or an object with
2975  *                                 a 'w' and 'h' properties.
2976  * imgURL - {String}
2977  * position - {String}
2978  * border - {String}
2979  * sizing - {String} 'crop', 'scale', or 'image'. Default is "scale"
2980  * opacity - {Float} Fractional value (0.0 - 1.0)
2981  */ 
2982 OpenLayers.Util.modifyAlphaImageDiv = function(div, id, px, sz, imgURL, 
2983                                                position, border, sizing, 
2984                                                opacity) {
2985
2986     OpenLayers.Util.modifyDOMElement(div, id, px, sz, position,
2987                                      null, null, opacity);
2988
2989     var img = div.childNodes[0];
2990
2991     if (imgURL) {
2992         img.src = imgURL;
2993     }
2994     OpenLayers.Util.modifyDOMElement(img, div.id + "_innerImage", null, sz, 
2995                                      "relative", border);
2996     
2997     if (OpenLayers.Util.alphaHack()) {
2998         if(div.style.display != "none") {
2999             div.style.display = "inline-block";
3000         }
3001         if (sizing == null) {
3002             sizing = "scale";
3003         }
3004         
3005         div.style.filter = "progid:DXImageTransform.Microsoft" +
3006                            ".AlphaImageLoader(src='" + img.src + "', " +
3007                            "sizingMethod='" + sizing + "')";
3008         if (parseFloat(div.style.opacity) >= 0.0 && 
3009             parseFloat(div.style.opacity) < 1.0) {
3010             div.style.filter += " alpha(opacity=" + div.style.opacity * 100 + ")";
3011         }
3012
3013         img.style.filter = "alpha(opacity=0)";
3014     }
3015 };
3016
3017 /** 
3018  * Function: createAlphaImageDiv
3019  * 
3020  * Parameters:
3021  * id - {String}
3022  * px - {<OpenLayers.Pixel>|Object} OpenLayers.Pixel or an object with
3023  *                                  a 'x' and 'y' properties.
3024  * sz - {<OpenLayers.Size>|Object} OpenLayers.Size or an object with
3025  *                                 a 'w' and 'h' properties.
3026  * imgURL - {String}
3027  * position - {String}
3028  * border - {String}
3029  * sizing - {String} 'crop', 'scale', or 'image'. Default is "scale"
3030  * opacity - {Float} Fractional value (0.0 - 1.0)
3031  * delayDisplay - {Boolean} If true waits until the image has been
3032  *                          loaded.
3033  * 
3034  * Returns:
3035  * {DOMElement} A DOM Div created with a DOM Image inside it. If the hack is 
3036  *              needed for transparency in IE, it is added.
3037  */ 
3038 OpenLayers.Util.createAlphaImageDiv = function(id, px, sz, imgURL, 
3039                                                position, border, sizing, 
3040                                                opacity, delayDisplay) {
3041     
3042     var div = OpenLayers.Util.createDiv();
3043     var img = OpenLayers.Util.createImage(null, null, null, null, null, null, 
3044                                           null, delayDisplay);
3045     img.className = "olAlphaImg";
3046     div.appendChild(img);
3047
3048     OpenLayers.Util.modifyAlphaImageDiv(div, id, px, sz, imgURL, position, 
3049                                         border, sizing, opacity);
3050     
3051     return div;
3052 };
3053
3054
3055 /** 
3056  * Function: upperCaseObject
3057  * Creates a new hashtable and copies over all the keys from the 
3058  *     passed-in object, but storing them under an uppercased
3059  *     version of the key at which they were stored.
3060  * 
3061  * Parameters: 
3062  * object - {Object}
3063  * 
3064  * Returns: 
3065  * {Object} A new Object with all the same keys but uppercased
3066  */
3067 OpenLayers.Util.upperCaseObject = function (object) {
3068     var uObject = {};
3069     for (var key in object) {
3070         uObject[key.toUpperCase()] = object[key];
3071     }
3072     return uObject;
3073 };
3074
3075 /** 
3076  * Function: applyDefaults
3077  * Takes an object and copies any properties that don't exist from
3078  *     another properties, by analogy with OpenLayers.Util.extend() from
3079  *     Prototype.js.
3080  * 
3081  * Parameters:
3082  * to - {Object} The destination object.
3083  * from - {Object} The source object.  Any properties of this object that
3084  *     are undefined in the to object will be set on the to object.
3085  *
3086  * Returns:
3087  * {Object} A reference to the to object.  Note that the to argument is modified
3088  *     in place and returned by this function.
3089  */
3090 OpenLayers.Util.applyDefaults = function (to, from) {
3091     to = to || {};
3092     /*
3093      * FF/Windows < 2.0.0.13 reports "Illegal operation on WrappedNative
3094      * prototype object" when calling hawOwnProperty if the source object is an
3095      * instance of window.Event.
3096      */
3097     var fromIsEvt = typeof window.Event == "function"
3098                     && from instanceof window.Event;
3099
3100     for (var key in from) {
3101         if (to[key] === undefined ||
3102             (!fromIsEvt && from.hasOwnProperty
3103              && from.hasOwnProperty(key) && !to.hasOwnProperty(key))) {
3104             to[key] = from[key];
3105         }
3106     }
3107     /**
3108      * IE doesn't include the toString property when iterating over an object's
3109      * properties with the for(property in object) syntax.  Explicitly check if
3110      * the source has its own toString property.
3111      */
3112     if(!fromIsEvt && from && from.hasOwnProperty
3113        && from.hasOwnProperty('toString') && !to.hasOwnProperty('toString')) {
3114         to.toString = from.toString;
3115     }
3116     
3117     return to;
3118 };
3119
3120 /**
3121  * Function: getParameterString
3122  * 
3123  * Parameters:
3124  * params - {Object}
3125  * 
3126  * Returns:
3127  * {String} A concatenation of the properties of an object in 
3128  *          http parameter notation. 
3129  *          (ex. <i>"key1=value1&key2=value2&key3=value3"</i>)
3130  *          If a parameter is actually a list, that parameter will then
3131  *          be set to a comma-seperated list of values (foo,bar) instead
3132  *          of being URL escaped (foo%3Abar). 
3133  */
3134 OpenLayers.Util.getParameterString = function(params) {
3135     var paramsArray = [];
3136     
3137     for (var key in params) {
3138       var value = params[key];
3139       if ((value != null) && (typeof value != 'function')) {
3140         var encodedValue;
3141         if (typeof value == 'object' && value.constructor == Array) {
3142           /* value is an array; encode items and separate with "," */
3143           var encodedItemArray = [];
3144           var item;
3145           for (var itemIndex=0, len=value.length; itemIndex<len; itemIndex++) {
3146             item = value[itemIndex];
3147             encodedItemArray.push(encodeURIComponent(
3148                 (item === null || item === undefined) ? "" : item)
3149             );
3150           }
3151           encodedValue = encodedItemArray.join(",");
3152         }
3153         else {
3154           /* value is a string; simply encode */
3155           encodedValue = encodeURIComponent(value);
3156         }
3157         paramsArray.push(encodeURIComponent(key) + "=" + encodedValue);
3158       }
3159     }
3160     
3161     return paramsArray.join("&");
3162 };
3163
3164 /**
3165  * Function: urlAppend
3166  * Appends a parameter string to a url. This function includes the logic for
3167  * using the appropriate character (none, & or ?) to append to the url before
3168  * appending the param string.
3169  * 
3170  * Parameters:
3171  * url - {String} The url to append to
3172  * paramStr - {String} The param string to append
3173  * 
3174  * Returns:
3175  * {String} The new url
3176  */
3177 OpenLayers.Util.urlAppend = function(url, paramStr) {
3178     var newUrl = url;
3179     if(paramStr) {
3180         var parts = (url + " ").split(/[?&]/);
3181         newUrl += (parts.pop() === " " ?
3182             paramStr :
3183             parts.length ? "&" + paramStr : "?" + paramStr);
3184     }
3185     return newUrl;
3186 };
3187
3188 /** 
3189  * Function: getImagesLocation
3190  * 
3191  * Returns:
3192  * {String} The fully formatted image location string
3193  */
3194 OpenLayers.Util.getImagesLocation = function() {
3195     return OpenLayers.ImgPath || (OpenLayers._getScriptLocation() + "img/");
3196 };
3197
3198 /** 
3199  * Function: getImageLocation
3200  * 
3201  * Returns:
3202  * {String} The fully formatted location string for a specified image
3203  */
3204 OpenLayers.Util.getImageLocation = function(image) {
3205     return OpenLayers.Util.getImagesLocation() + image;
3206 };
3207
3208
3209 /** 
3210  * Function: Try
3211  * Execute functions until one of them doesn't throw an error. 
3212  *     Capitalized because "try" is a reserved word in JavaScript.
3213  *     Taken directly from OpenLayers.Util.Try()
3214  * 
3215  * Parameters:
3216  * [*] - {Function} Any number of parameters may be passed to Try()
3217  *    It will attempt to execute each of them until one of them 
3218  *    successfully executes. 
3219  *    If none executes successfully, returns null.
3220  * 
3221  * Returns:
3222  * {*} The value returned by the first successfully executed function.
3223  */
3224 OpenLayers.Util.Try = function() {
3225     var returnValue = null;
3226
3227     for (var i=0, len=arguments.length; i<len; i++) {
3228       var lambda = arguments[i];
3229       try {
3230         returnValue = lambda();
3231         break;
3232       } catch (e) {}
3233     }
3234
3235     return returnValue;
3236 };
3237
3238 /**
3239  * Function: getXmlNodeValue
3240  * 
3241  * Parameters:
3242  * node - {XMLNode}
3243  * 
3244  * Returns:
3245  * {String} The text value of the given node, without breaking in firefox or IE
3246  */
3247 OpenLayers.Util.getXmlNodeValue = function(node) {
3248     var val = null;
3249     OpenLayers.Util.Try( 
3250         function() {
3251             val = node.text;
3252             if (!val) {
3253                 val = node.textContent;
3254             }
3255             if (!val) {
3256                 val = node.firstChild.nodeValue;
3257             }
3258         }, 
3259         function() {
3260             val = node.textContent;
3261         }); 
3262     return val;
3263 };
3264
3265 /** 
3266  * Function: mouseLeft
3267  * 
3268  * Parameters:
3269  * evt - {Event}
3270  * div - {HTMLDivElement}
3271  * 
3272  * Returns:
3273  * {Boolean}
3274  */
3275 OpenLayers.Util.mouseLeft = function (evt, div) {
3276     // start with the element to which the mouse has moved
3277     var target = (evt.relatedTarget) ? evt.relatedTarget : evt.toElement;
3278     // walk up the DOM tree.
3279     while (target != div && target != null) {
3280         target = target.parentNode;
3281     }
3282     // if the target we stop at isn't the div, then we've left the div.
3283     return (target != div);
3284 };
3285
3286 /**
3287  * Property: precision
3288  * {Number} The number of significant digits to retain to avoid
3289  * floating point precision errors.
3290  *
3291  * We use 14 as a "safe" default because, although IEEE 754 double floats
3292  * (standard on most modern operating systems) support up to about 16
3293  * significant digits, 14 significant digits are sufficient to represent
3294  * sub-millimeter accuracy in any coordinate system that anyone is likely to
3295  * use with OpenLayers.
3296  *
3297  * If DEFAULT_PRECISION is set to 0, the original non-truncating behavior
3298  * of OpenLayers <2.8 is preserved. Be aware that this will cause problems
3299  * with certain projections, e.g. spherical Mercator.
3300  *
3301  */
3302 OpenLayers.Util.DEFAULT_PRECISION = 14;
3303
3304 /**
3305  * Function: toFloat
3306  * Convenience method to cast an object to a Number, rounded to the
3307  * desired floating point precision.
3308  *
3309  * Parameters:
3310  * number    - {Number} The number to cast and round.
3311  * precision - {Number} An integer suitable for use with
3312  *      Number.toPrecision(). Defaults to OpenLayers.Util.DEFAULT_PRECISION.
3313  *      If set to 0, no rounding is performed.
3314  *
3315  * Returns:
3316  * {Number} The cast, rounded number.
3317  */
3318 OpenLayers.Util.toFloat = function (number, precision) {
3319     if (precision == null) {
3320         precision = OpenLayers.Util.DEFAULT_PRECISION;
3321     }
3322     if (typeof number !== "number") {
3323         number = parseFloat(number);
3324     }
3325     return precision === 0 ? number :
3326                              parseFloat(number.toPrecision(precision));
3327 };
3328
3329 /**
3330  * Function: rad
3331  * 
3332  * Parameters:
3333  * x - {Float}
3334  * 
3335  * Returns:
3336  * {Float}
3337  */
3338 OpenLayers.Util.rad = function(x) {return x*Math.PI/180;};
3339
3340 /**
3341  * Function: deg
3342  *
3343  * Parameters:
3344  * x - {Float}
3345  *
3346  * Returns:
3347  * {Float}
3348  */
3349 OpenLayers.Util.deg = function(x) {return x*180/Math.PI;};
3350
3351 /**
3352  * Property: VincentyConstants
3353  * {Object} Constants for Vincenty functions.
3354  */
3355 OpenLayers.Util.VincentyConstants = {
3356     a: 6378137,
3357     b: 6356752.3142,
3358     f: 1/298.257223563
3359 };
3360
3361 /**
3362  * APIFunction: distVincenty
3363  * Given two objects representing points with geographic coordinates, this
3364  *     calculates the distance between those points on the surface of an
3365  *     ellipsoid.
3366  *
3367  * Parameters:
3368  * p1 - {<OpenLayers.LonLat>} (or any object with both .lat, .lon properties)
3369  * p2 - {<OpenLayers.LonLat>} (or any object with both .lat, .lon properties)
3370  *
3371  * Returns:
3372  * {Float} The distance (in km) between the two input points as measured on an
3373  *     ellipsoid.  Note that the input point objects must be in geographic
3374  *     coordinates (decimal degrees) and the return distance is in kilometers.
3375  */
3376 OpenLayers.Util.distVincenty = function(p1, p2) {
3377     var ct = OpenLayers.Util.VincentyConstants;
3378     var a = ct.a, b = ct.b, f = ct.f;
3379
3380     var L = OpenLayers.Util.rad(p2.lon - p1.lon);
3381     var U1 = Math.atan((1-f) * Math.tan(OpenLayers.Util.rad(p1.lat)));
3382     var U2 = Math.atan((1-f) * Math.tan(OpenLayers.Util.rad(p2.lat)));
3383     var sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
3384     var sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);
3385     var lambda = L, lambdaP = 2*Math.PI;
3386     var iterLimit = 20;
3387     while (Math.abs(lambda-lambdaP) > 1e-12 && --iterLimit>0) {
3388         var sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda);
3389         var sinSigma = Math.sqrt((cosU2*sinLambda) * (cosU2*sinLambda) +
3390         (cosU1*sinU2-sinU1*cosU2*cosLambda) * (cosU1*sinU2-sinU1*cosU2*cosLambda));
3391         if (sinSigma==0) {
3392             return 0;  // co-incident points
3393         }
3394         var cosSigma = sinU1*sinU2 + cosU1*cosU2*cosLambda;
3395         var sigma = Math.atan2(sinSigma, cosSigma);
3396         var alpha = Math.asin(cosU1 * cosU2 * sinLambda / sinSigma);
3397         var cosSqAlpha = Math.cos(alpha) * Math.cos(alpha);
3398         var cos2SigmaM = cosSigma - 2*sinU1*sinU2/cosSqAlpha;
3399         var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
3400         lambdaP = lambda;
3401         lambda = L + (1-C) * f * Math.sin(alpha) *
3402         (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
3403     }
3404     if (iterLimit==0) {
3405         return NaN;  // formula failed to converge
3406     }
3407     var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
3408     var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
3409     var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
3410     var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
3411         B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
3412     var s = b*A*(sigma-deltaSigma);
3413     var d = s.toFixed(3)/1000; // round to 1mm precision
3414     return d;
3415 };
3416
3417 /**
3418  * APIFunction: destinationVincenty
3419  * Calculate destination point given start point lat/long (numeric degrees),
3420  * bearing (numeric degrees) & distance (in m).
3421  * Adapted from Chris Veness work, see
3422  * http://www.movable-type.co.uk/scripts/latlong-vincenty-direct.html
3423  *
3424  * Parameters:
3425  * lonlat  - {<OpenLayers.LonLat>} (or any object with both .lat, .lon
3426  *     properties) The start point.
3427  * brng     - {Float} The bearing (degrees).
3428  * dist     - {Float} The ground distance (meters).
3429  *
3430  * Returns:
3431  * {<OpenLayers.LonLat>} The destination point.
3432  */
3433 OpenLayers.Util.destinationVincenty = function(lonlat, brng, dist) {
3434     var u = OpenLayers.Util;
3435     var ct = u.VincentyConstants;
3436     var a = ct.a, b = ct.b, f = ct.f;
3437
3438     var lon1 = lonlat.lon;
3439     var lat1 = lonlat.lat;
3440
3441     var s = dist;
3442     var alpha1 = u.rad(brng);
3443     var sinAlpha1 = Math.sin(alpha1);
3444     var cosAlpha1 = Math.cos(alpha1);
3445
3446     var tanU1 = (1-f) * Math.tan(u.rad(lat1));
3447     var cosU1 = 1 / Math.sqrt((1 + tanU1*tanU1)), sinU1 = tanU1*cosU1;
3448     var sigma1 = Math.atan2(tanU1, cosAlpha1);
3449     var sinAlpha = cosU1 * sinAlpha1;
3450     var cosSqAlpha = 1 - sinAlpha*sinAlpha;
3451     var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
3452     var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
3453     var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
3454
3455     var sigma = s / (b*A), sigmaP = 2*Math.PI;
3456     while (Math.abs(sigma-sigmaP) > 1e-12) {
3457         var cos2SigmaM = Math.cos(2*sigma1 + sigma);
3458         var sinSigma = Math.sin(sigma);
3459         var cosSigma = Math.cos(sigma);
3460         var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
3461             B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
3462         sigmaP = sigma;
3463         sigma = s / (b*A) + deltaSigma;
3464     }
3465
3466     var tmp = sinU1*sinSigma - cosU1*cosSigma*cosAlpha1;
3467     var lat2 = Math.atan2(sinU1*cosSigma + cosU1*sinSigma*cosAlpha1,
3468         (1-f)*Math.sqrt(sinAlpha*sinAlpha + tmp*tmp));
3469     var lambda = Math.atan2(sinSigma*sinAlpha1, cosU1*cosSigma - sinU1*sinSigma*cosAlpha1);
3470     var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
3471     var L = lambda - (1-C) * f * sinAlpha *
3472         (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
3473
3474     var revAz = Math.atan2(sinAlpha, -tmp);  // final bearing
3475
3476     return new OpenLayers.LonLat(lon1+u.deg(L), u.deg(lat2));
3477 };
3478
3479 /**
3480  * Function: getParameters
3481  * Parse the parameters from a URL or from the current page itself into a 
3482  *     JavaScript Object. Note that parameter values with commas are separated
3483  *     out into an Array.
3484  * 
3485  * Parameters:
3486  * url - {String} Optional url used to extract the query string.
3487  *                If url is null or is not supplied, query string is taken 
3488  *                from the page location.
3489  * options - {Object} Additional options. Optional.
3490  *
3491  * Valid options:
3492  *   splitArgs - {Boolean} Split comma delimited params into arrays? Default is
3493  *       true.
3494  * 
3495  * Returns:
3496  * {Object} An object of key/value pairs from the query string.
3497  */
3498 OpenLayers.Util.getParameters = function(url, options) {
3499     options = options || {};
3500     // if no url specified, take it from the location bar
3501     url = (url === null || url === undefined) ? window.location.href : url;
3502
3503     //parse out parameters portion of url string
3504     var paramsString = "";
3505     if (OpenLayers.String.contains(url, '?')) {
3506         var start = url.indexOf('?') + 1;
3507         var end = OpenLayers.String.contains(url, "#") ?
3508                     url.indexOf('#') : url.length;
3509         paramsString = url.substring(start, end);
3510     }
3511
3512     var parameters = {};
3513     var pairs = paramsString.split(/[&;]/);
3514     for(var i=0, len=pairs.length; i<len; ++i) {
3515         var keyValue = pairs[i].split('=');
3516         if (keyValue[0]) {
3517
3518             var key = keyValue[0];
3519             try {
3520                 key = decodeURIComponent(key);
3521             } catch (err) {
3522                 key = unescape(key);
3523             }
3524             
3525             // being liberal by replacing "+" with " "
3526             var value = (keyValue[1] || '').replace(/\+/g, " ");
3527
3528             try {
3529                 value = decodeURIComponent(value);
3530             } catch (err) {
3531                 value = unescape(value);
3532             }
3533             
3534             // follow OGC convention of comma delimited values
3535             if (options.splitArgs !== false) {
3536                 value = value.split(",");
3537             }
3538
3539             //if there's only one value, do not return as array                    
3540             if (value.length == 1) {
3541                 value = value[0];
3542             }                
3543             
3544             parameters[key] = value;
3545          }
3546      }
3547     return parameters;
3548 };
3549
3550 /**
3551  * Property: lastSeqID
3552  * {Integer} The ever-incrementing count variable.
3553  *           Used for generating unique ids.
3554  */
3555 OpenLayers.Util.lastSeqID = 0;
3556
3557 /**
3558  * Function: createUniqueID
3559  * Create a unique identifier for this session.  Each time this function
3560  *     is called, a counter is incremented.  The return will be the optional
3561  *     prefix (defaults to "id_") appended with the counter value.
3562  * 
3563  * Parameters:
3564  * prefix - {String} Optional string to prefix unique id. Default is "id_".
3565  *     Note that dots (".") in the prefix will be replaced with underscore ("_").
3566  * 
3567  * Returns:
3568  * {String} A unique id string, built on the passed in prefix.
3569  */
3570 OpenLayers.Util.createUniqueID = function(prefix) {
3571     if (prefix == null) {
3572         prefix = "id_";
3573     } else {
3574         prefix = prefix.replace(OpenLayers.Util.dotless, "_");
3575     }
3576     OpenLayers.Util.lastSeqID += 1; 
3577     return prefix + OpenLayers.Util.lastSeqID;        
3578 };
3579
3580 /**
3581  * Constant: INCHES_PER_UNIT
3582  * {Object} Constant inches per unit -- borrowed from MapServer mapscale.c
3583  * derivation of nautical miles from http://en.wikipedia.org/wiki/Nautical_mile
3584  * Includes the full set of units supported by CS-MAP (http://trac.osgeo.org/csmap/)
3585  * and PROJ.4 (http://trac.osgeo.org/proj/)
3586  * The hardcoded table is maintain in a CS-MAP source code module named CSdataU.c
3587  * The hardcoded table of PROJ.4 units are in pj_units.c.
3588  */
3589 OpenLayers.INCHES_PER_UNIT = { 
3590     'inches': 1.0,
3591     'ft': 12.0,
3592     'mi': 63360.0,
3593     'm': 39.37,
3594     'km': 39370,
3595     'dd': 4374754,
3596     'yd': 36
3597 };
3598 OpenLayers.INCHES_PER_UNIT["in"]= OpenLayers.INCHES_PER_UNIT.inches;
3599 OpenLayers.INCHES_PER_UNIT["degrees"] = OpenLayers.INCHES_PER_UNIT.dd;
3600 OpenLayers.INCHES_PER_UNIT["nmi"] = 1852 * OpenLayers.INCHES_PER_UNIT.m;
3601
3602 // Units from CS-Map
3603 OpenLayers.METERS_PER_INCH = 0.02540005080010160020;
3604 OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT, {
3605     "Inch": OpenLayers.INCHES_PER_UNIT.inches,
3606     "Meter": 1.0 / OpenLayers.METERS_PER_INCH,   //EPSG:9001
3607     "Foot": 0.30480060960121920243 / OpenLayers.METERS_PER_INCH,   //EPSG:9003
3608     "IFoot": 0.30480000000000000000 / OpenLayers.METERS_PER_INCH,   //EPSG:9002
3609     "ClarkeFoot": 0.3047972651151 / OpenLayers.METERS_PER_INCH,   //EPSG:9005
3610     "SearsFoot": 0.30479947153867624624 / OpenLayers.METERS_PER_INCH,   //EPSG:9041
3611     "GoldCoastFoot": 0.30479971018150881758 / OpenLayers.METERS_PER_INCH,   //EPSG:9094
3612     "IInch": 0.02540000000000000000 / OpenLayers.METERS_PER_INCH,
3613     "MicroInch": 0.00002540000000000000 / OpenLayers.METERS_PER_INCH,
3614     "Mil": 0.00000002540000000000 / OpenLayers.METERS_PER_INCH,
3615     "Centimeter": 0.01000000000000000000 / OpenLayers.METERS_PER_INCH,
3616     "Kilometer": 1000.00000000000000000000 / OpenLayers.METERS_PER_INCH,   //EPSG:9036
3617     "Yard": 0.91440182880365760731 / OpenLayers.METERS_PER_INCH,
3618     "SearsYard": 0.914398414616029 / OpenLayers.METERS_PER_INCH,   //EPSG:9040
3619     "IndianYard": 0.91439853074444079983 / OpenLayers.METERS_PER_INCH,   //EPSG:9084
3620     "IndianYd37": 0.91439523 / OpenLayers.METERS_PER_INCH,   //EPSG:9085
3621     "IndianYd62": 0.9143988 / OpenLayers.METERS_PER_INCH,   //EPSG:9086
3622     "IndianYd75": 0.9143985 / OpenLayers.METERS_PER_INCH,   //EPSG:9087
3623     "IndianFoot": 0.30479951 / OpenLayers.METERS_PER_INCH,   //EPSG:9080
3624     "IndianFt37": 0.30479841 / OpenLayers.METERS_PER_INCH,   //EPSG:9081
3625     "IndianFt62": 0.3047996 / OpenLayers.METERS_PER_INCH,   //EPSG:9082
3626     "IndianFt75": 0.3047995 / OpenLayers.METERS_PER_INCH,   //EPSG:9083
3627     "Mile": 1609.34721869443738887477 / OpenLayers.METERS_PER_INCH,
3628     "IYard": 0.91440000000000000000 / OpenLayers.METERS_PER_INCH,   //EPSG:9096
3629     "IMile": 1609.34400000000000000000 / OpenLayers.METERS_PER_INCH,   //EPSG:9093
3630     "NautM": 1852.00000000000000000000 / OpenLayers.METERS_PER_INCH,   //EPSG:9030
3631     "Lat-66": 110943.316488932731 / OpenLayers.METERS_PER_INCH,
3632     "Lat-83": 110946.25736872234125 / OpenLayers.METERS_PER_INCH,
3633     "Decimeter": 0.10000000000000000000 / OpenLayers.METERS_PER_INCH,
3634     "Millimeter": 0.00100000000000000000 / OpenLayers.METERS_PER_INCH,
3635     "Dekameter": 10.00000000000000000000 / OpenLayers.METERS_PER_INCH,
3636     "Decameter": 10.00000000000000000000 / OpenLayers.METERS_PER_INCH,
3637     "Hectometer": 100.00000000000000000000 / OpenLayers.METERS_PER_INCH,
3638     "GermanMeter": 1.0000135965 / OpenLayers.METERS_PER_INCH,   //EPSG:9031
3639     "CaGrid": 0.999738 / OpenLayers.METERS_PER_INCH,
3640     "ClarkeChain": 20.1166194976 / OpenLayers.METERS_PER_INCH,   //EPSG:9038
3641     "GunterChain": 20.11684023368047 / OpenLayers.METERS_PER_INCH,   //EPSG:9033
3642     "BenoitChain": 20.116782494375872 / OpenLayers.METERS_PER_INCH,   //EPSG:9062
3643     "SearsChain": 20.11676512155 / OpenLayers.METERS_PER_INCH,   //EPSG:9042
3644     "ClarkeLink": 0.201166194976 / OpenLayers.METERS_PER_INCH,   //EPSG:9039
3645     "GunterLink": 0.2011684023368047 / OpenLayers.METERS_PER_INCH,   //EPSG:9034
3646     "BenoitLink": 0.20116782494375872 / OpenLayers.METERS_PER_INCH,   //EPSG:9063
3647     "SearsLink": 0.2011676512155 / OpenLayers.METERS_PER_INCH,   //EPSG:9043
3648     "Rod": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
3649     "IntnlChain": 20.1168 / OpenLayers.METERS_PER_INCH,   //EPSG:9097
3650     "IntnlLink": 0.201168 / OpenLayers.METERS_PER_INCH,   //EPSG:9098
3651     "Perch": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
3652     "Pole": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
3653     "Furlong": 201.1684023368046 / OpenLayers.METERS_PER_INCH,
3654     "Rood": 3.778266898 / OpenLayers.METERS_PER_INCH,
3655     "CapeFoot": 0.3047972615 / OpenLayers.METERS_PER_INCH,
3656     "Brealey": 375.00000000000000000000 / OpenLayers.METERS_PER_INCH,
3657     "ModAmFt": 0.304812252984505969011938 / OpenLayers.METERS_PER_INCH,
3658     "Fathom": 1.8288 / OpenLayers.METERS_PER_INCH,
3659     "NautM-UK": 1853.184 / OpenLayers.METERS_PER_INCH,
3660     "50kilometers": 50000.0 / OpenLayers.METERS_PER_INCH,
3661     "150kilometers": 150000.0 / OpenLayers.METERS_PER_INCH
3662 });
3663
3664 //unit abbreviations supported by PROJ.4
3665 OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT, {
3666     "mm": OpenLayers.INCHES_PER_UNIT["Meter"] / 1000.0,
3667     "cm": OpenLayers.INCHES_PER_UNIT["Meter"] / 100.0,
3668     "dm": OpenLayers.INCHES_PER_UNIT["Meter"] * 100.0,
3669     "km": OpenLayers.INCHES_PER_UNIT["Meter"] * 1000.0,
3670     "kmi": OpenLayers.INCHES_PER_UNIT["nmi"],    //International Nautical Mile
3671     "fath": OpenLayers.INCHES_PER_UNIT["Fathom"], //International Fathom
3672     "ch": OpenLayers.INCHES_PER_UNIT["IntnlChain"],  //International Chain
3673     "link": OpenLayers.INCHES_PER_UNIT["IntnlLink"], //International Link
3674     "us-in": OpenLayers.INCHES_PER_UNIT["inches"], //U.S. Surveyor's Inch
3675     "us-ft": OpenLayers.INCHES_PER_UNIT["Foot"], //U.S. Surveyor's Foot
3676     "us-yd": OpenLayers.INCHES_PER_UNIT["Yard"], //U.S. Surveyor's Yard
3677     "us-ch": OpenLayers.INCHES_PER_UNIT["GunterChain"], //U.S. Surveyor's Chain
3678     "us-mi": OpenLayers.INCHES_PER_UNIT["Mile"],   //U.S. Surveyor's Statute Mile
3679     "ind-yd": OpenLayers.INCHES_PER_UNIT["IndianYd37"],  //Indian Yard
3680     "ind-ft": OpenLayers.INCHES_PER_UNIT["IndianFt37"],  //Indian Foot
3681     "ind-ch": 20.11669506 / OpenLayers.METERS_PER_INCH  //Indian Chain
3682 });
3683
3684 /** 
3685  * Constant: DOTS_PER_INCH
3686  * {Integer} 72 (A sensible default)
3687  */
3688 OpenLayers.DOTS_PER_INCH = 72;
3689
3690 /**
3691  * Function: normalizeScale
3692  * 
3693  * Parameters:
3694  * scale - {float}
3695  * 
3696  * Returns:
3697  * {Float} A normalized scale value, in 1 / X format. 
3698  *         This means that if a value less than one ( already 1/x) is passed
3699  *         in, it just returns scale directly. Otherwise, it returns 
3700  *         1 / scale
3701  */
3702 OpenLayers.Util.normalizeScale = function (scale) {
3703     var normScale = (scale > 1.0) ? (1.0 / scale) 
3704                                   : scale;
3705     return normScale;
3706 };
3707
3708 /**
3709  * Function: getResolutionFromScale
3710  * 
3711  * Parameters:
3712  * scale - {Float}
3713  * units - {String} Index into OpenLayers.INCHES_PER_UNIT hashtable.
3714  *                  Default is degrees
3715  * 
3716  * Returns:
3717  * {Float} The corresponding resolution given passed-in scale and unit 
3718  *     parameters.  If the given scale is falsey, the returned resolution will
3719  *     be undefined.
3720  */
3721 OpenLayers.Util.getResolutionFromScale = function (scale, units) {
3722     var resolution;
3723     if (scale) {
3724         if (units == null) {
3725             units = "degrees";
3726         }
3727         var normScale = OpenLayers.Util.normalizeScale(scale);
3728         resolution = 1 / (normScale * OpenLayers.INCHES_PER_UNIT[units]
3729                                         * OpenLayers.DOTS_PER_INCH);        
3730     }
3731     return resolution;
3732 };
3733
3734 /**
3735  * Function: getScaleFromResolution
3736  * 
3737  * Parameters:
3738  * resolution - {Float}
3739  * units - {String} Index into OpenLayers.INCHES_PER_UNIT hashtable.
3740  *                  Default is degrees
3741  * 
3742  * Returns:
3743  * {Float} The corresponding scale given passed-in resolution and unit 
3744  *         parameters.
3745  */
3746 OpenLayers.Util.getScaleFromResolution = function (resolution, units) {
3747
3748     if (units == null) {
3749         units = "degrees";
3750     }
3751
3752     var scale = resolution * OpenLayers.INCHES_PER_UNIT[units] *
3753                     OpenLayers.DOTS_PER_INCH;
3754     return scale;
3755 };
3756
3757 /**
3758  * Function: pagePosition
3759  * Calculates the position of an element on the page (see
3760  * http://code.google.com/p/doctype/wiki/ArticlePageOffset)
3761  *
3762  * OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is
3763  * Copyright (c) 2006, Yahoo! Inc.
3764  * All rights reserved.
3765  * 
3766  * Redistribution and use of this software in source and binary forms, with or
3767  * without modification, are permitted provided that the following conditions
3768  * are met:
3769  * 
3770  * * Redistributions of source code must retain the above copyright notice,
3771  *   this list of conditions and the following disclaimer.
3772  * 
3773  * * Redistributions in binary form must reproduce the above copyright notice,
3774  *   this list of conditions and the following disclaimer in the documentation
3775  *   and/or other materials provided with the distribution.
3776  * 
3777  * * Neither the name of Yahoo! Inc. nor the names of its contributors may be
3778  *   used to endorse or promote products derived from this software without
3779  *   specific prior written permission of Yahoo! Inc.
3780  * 
3781  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
3782  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
3783  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
3784  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
3785  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
3786  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
3787  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
3788  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
3789  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
3790  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
3791  * POSSIBILITY OF SUCH DAMAGE.
3792  *
3793  * Parameters:
3794  * forElement - {DOMElement}
3795  * 
3796  * Returns:
3797  * {Array} two item array, Left value then Top value.
3798  */
3799 OpenLayers.Util.pagePosition =  function(forElement) {
3800     // NOTE: If element is hidden (display none or disconnected or any the
3801     // ancestors are hidden) we get (0,0) by default but we still do the
3802     // accumulation of scroll position.
3803
3804     var pos = [0, 0];
3805     var viewportElement = OpenLayers.Util.getViewportElement();
3806     if (!forElement || forElement == window || forElement == viewportElement) {
3807         // viewport is always at 0,0 as that defined the coordinate system for
3808         // this function - this avoids special case checks in the code below
3809         return pos;
3810     }
3811
3812     // Gecko browsers normally use getBoxObjectFor to calculate the position.
3813     // When invoked for an element with an implicit absolute position though it
3814     // can be off by one. Therefore the recursive implementation is used in
3815     // those (relatively rare) cases.
3816     var BUGGY_GECKO_BOX_OBJECT =
3817         OpenLayers.IS_GECKO && document.getBoxObjectFor &&
3818         OpenLayers.Element.getStyle(forElement, 'position') == 'absolute' &&
3819         (forElement.style.top == '' || forElement.style.left == '');
3820
3821     var parent = null;
3822     var box;
3823
3824     if (forElement.getBoundingClientRect) { // IE
3825         box = forElement.getBoundingClientRect();
3826         var scrollTop = window.pageYOffset || viewportElement.scrollTop;
3827         var scrollLeft = window.pageXOffset || viewportElement.scrollLeft;
3828         
3829         pos[0] = box.left + scrollLeft;
3830         pos[1] = box.top + scrollTop;
3831
3832     } else if (document.getBoxObjectFor && !BUGGY_GECKO_BOX_OBJECT) { // gecko
3833         // Gecko ignores the scroll values for ancestors, up to 1.9.  See:
3834         // https://bugzilla.mozilla.org/show_bug.cgi?id=328881 and
3835         // https://bugzilla.mozilla.org/show_bug.cgi?id=330619
3836
3837         box = document.getBoxObjectFor(forElement);
3838         var vpBox = document.getBoxObjectFor(viewportElement);
3839         pos[0] = box.screenX - vpBox.screenX;
3840         pos[1] = box.screenY - vpBox.screenY;
3841
3842     } else { // safari/opera
3843         pos[0] = forElement.offsetLeft;
3844         pos[1] = forElement.offsetTop;
3845         parent = forElement.offsetParent;
3846         if (parent != forElement) {
3847             while (parent) {
3848                 pos[0] += parent.offsetLeft;
3849                 pos[1] += parent.offsetTop;
3850                 parent = parent.offsetParent;
3851             }
3852         }
3853
3854         var browser = OpenLayers.BROWSER_NAME;
3855
3856         // opera & (safari absolute) incorrectly account for body offsetTop
3857         if (browser == "opera" || (browser == "safari" &&
3858               OpenLayers.Element.getStyle(forElement, 'position') == 'absolute')) {
3859             pos[1] -= document.body.offsetTop;
3860         }
3861
3862         // accumulate the scroll positions for everything but the body element
3863         parent = forElement.offsetParent;
3864         while (parent && parent != document.body) {
3865             pos[0] -= parent.scrollLeft;
3866             // see https://bugs.opera.com/show_bug.cgi?id=249965
3867             if (browser != "opera" || parent.tagName != 'TR') {
3868                 pos[1] -= parent.scrollTop;
3869             }
3870             parent = parent.offsetParent;
3871         }
3872     }
3873     
3874     return pos;
3875 };
3876
3877 /**
3878  * Function: getViewportElement
3879  * Returns die viewport element of the document. The viewport element is
3880  * usually document.documentElement, except in IE,where it is either
3881  * document.body or document.documentElement, depending on the document's
3882  * compatibility mode (see
3883  * http://code.google.com/p/doctype/wiki/ArticleClientViewportElement)
3884  *
3885  * Returns:
3886  * {DOMElement}
3887  */
3888 OpenLayers.Util.getViewportElement = function() {
3889     var viewportElement = arguments.callee.viewportElement;
3890     if (viewportElement == undefined) {
3891         viewportElement = (OpenLayers.BROWSER_NAME == "msie" &&
3892             document.compatMode != 'CSS1Compat') ? document.body :
3893             document.documentElement;
3894         arguments.callee.viewportElement = viewportElement;
3895     }
3896     return viewportElement;
3897 };
3898
3899 /** 
3900  * Function: isEquivalentUrl
3901  * Test two URLs for equivalence. 
3902  * 
3903  * Setting 'ignoreCase' allows for case-independent comparison.
3904  * 
3905  * Comparison is based on: 
3906  *  - Protocol
3907  *  - Host (evaluated without the port)
3908  *  - Port (set 'ignorePort80' to ignore "80" values)
3909  *  - Hash ( set 'ignoreHash' to disable)
3910  *  - Pathname (for relative <-> absolute comparison) 
3911  *  - Arguments (so they can be out of order)
3912  *  
3913  * Parameters:
3914  * url1 - {String}
3915  * url2 - {String}
3916  * options - {Object} Allows for customization of comparison:
3917  *                    'ignoreCase' - Default is True
3918  *                    'ignorePort80' - Default is True
3919  *                    'ignoreHash' - Default is True
3920  *
3921  * Returns:
3922  * {Boolean} Whether or not the two URLs are equivalent
3923  */
3924 OpenLayers.Util.isEquivalentUrl = function(url1, url2, options) {
3925     options = options || {};
3926
3927     OpenLayers.Util.applyDefaults(options, {
3928         ignoreCase: true,
3929         ignorePort80: true,
3930         ignoreHash: true,
3931         splitArgs: false
3932     });
3933
3934     var urlObj1 = OpenLayers.Util.createUrlObject(url1, options);
3935     var urlObj2 = OpenLayers.Util.createUrlObject(url2, options);
3936
3937     //compare all keys except for "args" (treated below)
3938     for(var key in urlObj1) {
3939         if(key !== "args") {
3940             if(urlObj1[key] != urlObj2[key]) {
3941                 return false;
3942             }
3943         }
3944     }
3945
3946     // compare search args - irrespective of order
3947     for(var key in urlObj1.args) {
3948         if(urlObj1.args[key] != urlObj2.args[key]) {
3949             return false;
3950         }
3951         delete urlObj2.args[key];
3952     }
3953     // urlObj2 shouldn't have any args left
3954     for(var key in urlObj2.args) {
3955         return false;
3956     }
3957     
3958     return true;
3959 };
3960
3961 /**
3962  * Function: createUrlObject
3963  * 
3964  * Parameters:
3965  * url - {String}
3966  * options - {Object} A hash of options.
3967  *
3968  * Valid options:
3969  *   ignoreCase - {Boolean} lowercase url,
3970  *   ignorePort80 - {Boolean} don't include explicit port if port is 80,
3971  *   ignoreHash - {Boolean} Don't include part of url after the hash (#).
3972  *   splitArgs - {Boolean} Split comma delimited params into arrays? Default is
3973  *       true.
3974  * 
3975  * Returns:
3976  * {Object} An object with separate url, a, port, host, and args parsed out 
3977  *          and ready for comparison
3978  */
3979 OpenLayers.Util.createUrlObject = function(url, options) {
3980     options = options || {};
3981
3982     // deal with relative urls first
3983     if(!(/^\w+:\/\//).test(url)) {
3984         var loc = window.location;
3985         var port = loc.port ? ":" + loc.port : "";
3986         var fullUrl = loc.protocol + "//" + loc.host.split(":").shift() + port;
3987         if(url.indexOf("/") === 0) {
3988             // full pathname
3989             url = fullUrl + url;
3990         } else {
3991             // relative to current path
3992             var parts = loc.pathname.split("/");
3993             parts.pop();
3994             url = fullUrl + parts.join("/") + "/" + url;
3995         }
3996     }
3997   
3998     if (options.ignoreCase) {
3999         url = url.toLowerCase(); 
4000     }
4001
4002     var a = document.createElement('a');
4003     a.href = url;
4004     
4005     var urlObject = {};
4006     
4007     //host (without port)
4008     urlObject.host = a.host.split(":").shift();
4009
4010     //protocol
4011     urlObject.protocol = a.protocol;  
4012
4013     //port (get uniform browser behavior with port 80 here)
4014     if(options.ignorePort80) {
4015         urlObject.port = (a.port == "80" || a.port == "0") ? "" : a.port;
4016     } else {
4017         urlObject.port = (a.port == "" || a.port == "0") ? "80" : a.port;
4018     }
4019
4020     //hash
4021     urlObject.hash = (options.ignoreHash || a.hash === "#") ? "" : a.hash;  
4022     
4023     //args
4024     var queryString = a.search;
4025     if (!queryString) {
4026         var qMark = url.indexOf("?");
4027         queryString = (qMark != -1) ? url.substr(qMark) : "";
4028     }
4029     urlObject.args = OpenLayers.Util.getParameters(queryString,
4030             {splitArgs: options.splitArgs});
4031
4032     // pathname
4033     //
4034     // This is a workaround for Internet Explorer where
4035     // window.location.pathname has a leading "/", but
4036     // a.pathname has no leading "/".
4037     urlObject.pathname = (a.pathname.charAt(0) == "/") ? a.pathname : "/" + a.pathname;
4038     
4039     return urlObject; 
4040 };
4041  
4042 /**
4043  * Function: removeTail
4044  * Takes a url and removes everything after the ? and #
4045  * 
4046  * Parameters:
4047  * url - {String} The url to process
4048  * 
4049  * Returns:
4050  * {String} The string with all queryString and Hash removed
4051  */
4052 OpenLayers.Util.removeTail = function(url) {
4053     var head = null;
4054     
4055     var qMark = url.indexOf("?");
4056     var hashMark = url.indexOf("#");
4057
4058     if (qMark == -1) {
4059         head = (hashMark != -1) ? url.substr(0,hashMark) : url;
4060     } else {
4061         head = (hashMark != -1) ? url.substr(0,Math.min(qMark, hashMark)) 
4062                                   : url.substr(0, qMark);
4063     }
4064     return head;
4065 };
4066
4067 /**
4068  * Constant: IS_GECKO
4069  * {Boolean} True if the userAgent reports the browser to use the Gecko engine
4070  */
4071 OpenLayers.IS_GECKO = (function() {
4072     var ua = navigator.userAgent.toLowerCase();
4073     return ua.indexOf("webkit") == -1 && ua.indexOf("gecko") != -1;
4074 })();
4075
4076 /**
4077  * Constant: CANVAS_SUPPORTED
4078  * {Boolean} True if canvas 2d is supported.
4079  */
4080 OpenLayers.CANVAS_SUPPORTED = (function() {
4081     var elem = document.createElement('canvas');
4082     return !!(elem.getContext && elem.getContext('2d'));
4083 })();
4084
4085 /**
4086  * Constant: BROWSER_NAME
4087  * {String}
4088  * A substring of the navigator.userAgent property.  Depending on the userAgent
4089  *     property, this will be the empty string or one of the following:
4090  *     * "opera" -- Opera
4091  *     * "msie"  -- Internet Explorer
4092  *     * "safari" -- Safari
4093  *     * "firefox" -- Firefox
4094  *     * "mozilla" -- Mozilla
4095  */
4096 OpenLayers.BROWSER_NAME = (function() {
4097     var name = "";
4098     var ua = navigator.userAgent.toLowerCase();
4099     if (ua.indexOf("opera") != -1) {
4100         name = "opera";
4101     } else if (ua.indexOf("msie") != -1) {
4102         name = "msie";
4103     } else if (ua.indexOf("safari") != -1) {
4104         name = "safari";
4105     } else if (ua.indexOf("mozilla") != -1) {
4106         if (ua.indexOf("firefox") != -1) {
4107             name = "firefox";
4108         } else {
4109             name = "mozilla";
4110         }
4111     }
4112     return name;
4113 })();
4114
4115 /**
4116  * Function: getBrowserName
4117  * 
4118  * Returns:
4119  * {String} A string which specifies which is the current 
4120  *          browser in which we are running. 
4121  * 
4122  *          Currently-supported browser detection and codes:
4123  *           * 'opera' -- Opera
4124  *           * 'msie'  -- Internet Explorer
4125  *           * 'safari' -- Safari
4126  *           * 'firefox' -- Firefox
4127  *           * 'mozilla' -- Mozilla
4128  * 
4129  *          If we are unable to property identify the browser, we 
4130  *           return an empty string.
4131  */
4132 OpenLayers.Util.getBrowserName = function() {
4133     return OpenLayers.BROWSER_NAME;
4134 };
4135
4136 /**
4137  * Method: getRenderedDimensions
4138  * Renders the contentHTML offscreen to determine actual dimensions for
4139  *     popup sizing. As we need layout to determine dimensions the content
4140  *     is rendered -9999px to the left and absolute to ensure the 
4141  *     scrollbars do not flicker
4142  *     
4143  * Parameters:
4144  * contentHTML
4145  * size - {<OpenLayers.Size>} If either the 'w' or 'h' properties is 
4146  *     specified, we fix that dimension of the div to be measured. This is 
4147  *     useful in the case where we have a limit in one dimension and must 
4148  *     therefore meaure the flow in the other dimension.
4149  * options - {Object}
4150  *
4151  * Allowed Options:
4152  *     displayClass - {String} Optional parameter.  A CSS class name(s) string
4153  *         to provide the CSS context of the rendered content.
4154  *     containerElement - {DOMElement} Optional parameter. Insert the HTML to 
4155  *         this node instead of the body root when calculating dimensions. 
4156  * 
4157  * Returns:
4158  * {<OpenLayers.Size>}
4159  */
4160 OpenLayers.Util.getRenderedDimensions = function(contentHTML, size, options) {
4161     
4162     var w, h;
4163     
4164     // create temp container div with restricted size
4165     var container = document.createElement("div");
4166     container.style.visibility = "hidden";
4167         
4168     var containerElement = (options && options.containerElement) 
4169         ? options.containerElement : document.body;
4170     
4171     // Opera and IE7 can't handle a node with position:aboslute if it inherits
4172     // position:absolute from a parent.
4173     var parentHasPositionAbsolute = false;
4174     var superContainer = null;
4175     var parent = containerElement;
4176     while (parent && parent.tagName.toLowerCase()!="body") {
4177         var parentPosition = OpenLayers.Element.getStyle(parent, "position");
4178         if(parentPosition == "absolute") {
4179             parentHasPositionAbsolute = true;
4180             break;
4181         } else if (parentPosition && parentPosition != "static") {
4182             break;
4183         }
4184         parent = parent.parentNode;
4185     }
4186     if(parentHasPositionAbsolute && (containerElement.clientHeight === 0 || 
4187                                      containerElement.clientWidth === 0) ){
4188         superContainer = document.createElement("div");
4189         superContainer.style.visibility = "hidden";
4190         superContainer.style.position = "absolute";
4191         superContainer.style.overflow = "visible";
4192         superContainer.style.width = document.body.clientWidth + "px";
4193         superContainer.style.height = document.body.clientHeight + "px";
4194         superContainer.appendChild(container);
4195     }
4196     container.style.position = "absolute";
4197
4198     //fix a dimension, if specified.
4199     if (size) {
4200         if (size.w) {
4201             w = size.w;
4202             container.style.width = w + "px";
4203         } else if (size.h) {
4204             h = size.h;
4205             container.style.height = h + "px";
4206         }
4207     }
4208
4209     //add css classes, if specified
4210     if (options && options.displayClass) {
4211         container.className = options.displayClass;
4212     }
4213     
4214     // create temp content div and assign content
4215     var content = document.createElement("div");
4216     content.innerHTML = contentHTML;
4217     
4218     // we need overflow visible when calculating the size
4219     content.style.overflow = "visible";
4220     if (content.childNodes) {
4221         for (var i=0, l=content.childNodes.length; i<l; i++) {
4222             if (!content.childNodes[i].style) continue;
4223             content.childNodes[i].style.overflow = "visible";
4224         }
4225     }
4226     
4227     // add content to restricted container 
4228     container.appendChild(content);
4229     
4230     // append container to body for rendering
4231     if (superContainer) {
4232         containerElement.appendChild(superContainer);
4233     } else {
4234         containerElement.appendChild(container);
4235     }
4236     
4237     // calculate scroll width of content and add corners and shadow width
4238     if (!w) {
4239         w = parseInt(content.scrollWidth);
4240     
4241         // update container width to allow height to adjust
4242         container.style.width = w + "px";
4243     }        
4244     // capture height and add shadow and corner image widths
4245     if (!h) {
4246         h = parseInt(content.scrollHeight);
4247     }
4248
4249     // remove elements
4250     container.removeChild(content);
4251     if (superContainer) {
4252         superContainer.removeChild(container);
4253         containerElement.removeChild(superContainer);
4254     } else {
4255         containerElement.removeChild(container);
4256     }
4257     
4258     return new OpenLayers.Size(w, h);
4259 };
4260
4261 /**
4262  * APIFunction: getScrollbarWidth
4263  * This function has been modified by the OpenLayers from the original version,
4264  *     written by Matthew Eernisse and released under the Apache 2 
4265  *     license here:
4266  * 
4267  *     http://www.fleegix.org/articles/2006/05/30/getting-the-scrollbar-width-in-pixels
4268  * 
4269  *     It has been modified simply to cache its value, since it is physically 
4270  *     impossible that this code could ever run in more than one browser at 
4271  *     once. 
4272  * 
4273  * Returns:
4274  * {Integer}
4275  */
4276 OpenLayers.Util.getScrollbarWidth = function() {
4277     
4278     var scrollbarWidth = OpenLayers.Util._scrollbarWidth;
4279     
4280     if (scrollbarWidth == null) {
4281         var scr = null;
4282         var inn = null;
4283         var wNoScroll = 0;
4284         var wScroll = 0;
4285     
4286         // Outer scrolling div
4287         scr = document.createElement('div');
4288         scr.style.position = 'absolute';
4289         scr.style.top = '-1000px';
4290         scr.style.left = '-1000px';
4291         scr.style.width = '100px';
4292         scr.style.height = '50px';
4293         // Start with no scrollbar
4294         scr.style.overflow = 'hidden';
4295     
4296         // Inner content div
4297         inn = document.createElement('div');
4298         inn.style.width = '100%';
4299         inn.style.height = '200px';
4300     
4301         // Put the inner div in the scrolling div
4302         scr.appendChild(inn);
4303         // Append the scrolling div to the doc
4304         document.body.appendChild(scr);
4305     
4306         // Width of the inner div sans scrollbar
4307         wNoScroll = inn.offsetWidth;
4308     
4309         // Add the scrollbar
4310         scr.style.overflow = 'scroll';
4311         // Width of the inner div width scrollbar
4312         wScroll = inn.offsetWidth;
4313     
4314         // Remove the scrolling div from the doc
4315         document.body.removeChild(document.body.lastChild);
4316     
4317         // Pixel width of the scroller
4318         OpenLayers.Util._scrollbarWidth = (wNoScroll - wScroll);
4319         scrollbarWidth = OpenLayers.Util._scrollbarWidth;
4320     }
4321
4322     return scrollbarWidth;
4323 };
4324
4325 /**
4326  * APIFunction: getFormattedLonLat
4327  * This function will return latitude or longitude value formatted as 
4328  *
4329  * Parameters:
4330  * coordinate - {Float} the coordinate value to be formatted
4331  * axis - {String} value of either 'lat' or 'lon' to indicate which axis is to
4332  *          to be formatted (default = lat)
4333  * dmsOption - {String} specify the precision of the output can be one of:
4334  *           'dms' show degrees minutes and seconds
4335  *           'dm' show only degrees and minutes
4336  *           'd' show only degrees
4337  * 
4338  * Returns:
4339  * {String} the coordinate value formatted as a string
4340  */
4341 OpenLayers.Util.getFormattedLonLat = function(coordinate, axis, dmsOption) {
4342     if (!dmsOption) {
4343         dmsOption = 'dms';    //default to show degree, minutes, seconds
4344     }
4345
4346     coordinate = (coordinate+540)%360 - 180; // normalize for sphere being round
4347
4348     var abscoordinate = Math.abs(coordinate);
4349     var coordinatedegrees = Math.floor(abscoordinate);
4350
4351     var coordinateminutes = (abscoordinate - coordinatedegrees)/(1/60);
4352     var tempcoordinateminutes = coordinateminutes;
4353     coordinateminutes = Math.floor(coordinateminutes);
4354     var coordinateseconds = (tempcoordinateminutes - coordinateminutes)/(1/60);
4355     coordinateseconds =  Math.round(coordinateseconds*10);
4356     coordinateseconds /= 10;
4357
4358     if( coordinateseconds >= 60) { 
4359         coordinateseconds -= 60; 
4360         coordinateminutes += 1; 
4361         if( coordinateminutes >= 60) { 
4362             coordinateminutes -= 60; 
4363             coordinatedegrees += 1; 
4364         } 
4365     }
4366     
4367     if( coordinatedegrees < 10 ) {
4368         coordinatedegrees = "0" + coordinatedegrees;
4369     }
4370     var str = coordinatedegrees + "\u00B0";
4371
4372     if (dmsOption.indexOf('dm') >= 0) {
4373         if( coordinateminutes < 10 ) {
4374             coordinateminutes = "0" + coordinateminutes;
4375         }
4376         str += coordinateminutes + "'";
4377   
4378         if (dmsOption.indexOf('dms') >= 0) {
4379             if( coordinateseconds < 10 ) {
4380                 coordinateseconds = "0" + coordinateseconds;
4381             }
4382             str += coordinateseconds + '"';
4383         }
4384     }
4385     
4386     if (axis == "lon") {
4387         str += coordinate < 0 ? OpenLayers.i18n("W") : OpenLayers.i18n("E");
4388     } else {
4389         str += coordinate < 0 ? OpenLayers.i18n("S") : OpenLayers.i18n("N");
4390     }
4391     return str;
4392 };
4393
4394 /**
4395  * Function: getConstructor
4396  * Take an OpenLayers style CLASS_NAME and return a constructor.
4397  *
4398  * Parameters:
4399  * className - {String} The dot delimited class name (e.g. 'OpenLayers.Foo').
4400  * 
4401  * Returns:
4402  * {Function} The constructor.
4403  */
4404 OpenLayers.Util.getConstructor = function(className) {
4405     var Constructor;
4406     var parts = className.split('.');
4407     if (parts[0] === "OpenLayers") {
4408         Constructor = OpenLayers;
4409     } else {
4410         // someone extended our base class and used their own namespace
4411         // this will not work when the library is evaluated in a closure
4412         // but it is the best we can do (until we ourselves provide a global)
4413         Constructor = window[parts[0]];
4414     }
4415     for (var i = 1, ii = parts.length; i < ii; ++i) {
4416         Constructor = Constructor[parts[i]];
4417     }
4418     return Constructor;
4419 };
4420 /* ======================================================================
4421     OpenLayers/Events.js
4422    ====================================================================== */
4423
4424 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
4425  * full list of contributors). Published under the 2-clause BSD license.
4426  * See license.txt in the OpenLayers distribution or repository for the
4427  * full text of the license. */
4428
4429
4430 /**
4431  * @requires OpenLayers/Util.js
4432  */
4433
4434 /**
4435  * Namespace: OpenLayers.Event
4436  * Utility functions for event handling.
4437  */
4438 OpenLayers.Event = {
4439
4440     /** 
4441      * Property: observers 
4442      * {Object} A hashtable cache of the event observers. Keyed by
4443      * element._eventCacheID 
4444      */
4445     observers: false,
4446
4447     /**
4448      * Constant: KEY_SPACE
4449      * {int}
4450      */
4451     KEY_SPACE: 32,
4452     
4453     /** 
4454      * Constant: KEY_BACKSPACE 
4455      * {int} 
4456      */
4457     KEY_BACKSPACE: 8,
4458
4459     /** 
4460      * Constant: KEY_TAB 
4461      * {int} 
4462      */
4463     KEY_TAB: 9,
4464
4465     /** 
4466      * Constant: KEY_RETURN 
4467      * {int} 
4468      */
4469     KEY_RETURN: 13,
4470
4471     /** 
4472      * Constant: KEY_ESC 
4473      * {int} 
4474      */
4475     KEY_ESC: 27,
4476
4477     /** 
4478      * Constant: KEY_LEFT 
4479      * {int} 
4480      */
4481     KEY_LEFT: 37,
4482
4483     /** 
4484      * Constant: KEY_UP 
4485      * {int} 
4486      */
4487     KEY_UP: 38,
4488
4489     /** 
4490      * Constant: KEY_RIGHT 
4491      * {int} 
4492      */
4493     KEY_RIGHT: 39,
4494
4495     /** 
4496      * Constant: KEY_DOWN 
4497      * {int} 
4498      */
4499     KEY_DOWN: 40,
4500
4501     /** 
4502      * Constant: KEY_DELETE 
4503      * {int} 
4504      */
4505     KEY_DELETE: 46,
4506
4507
4508     /**
4509      * Method: element
4510      * Cross browser event element detection.
4511      * 
4512      * Parameters:
4513      * event - {Event} 
4514      * 
4515      * Returns:
4516      * {DOMElement} The element that caused the event 
4517      */
4518     element: function(event) {
4519         return event.target || event.srcElement;
4520     },
4521
4522     /**
4523      * Method: isSingleTouch
4524      * Determine whether event was caused by a single touch
4525      *
4526      * Parameters:
4527      * event - {Event}
4528      *
4529      * Returns:
4530      * {Boolean}
4531      */
4532     isSingleTouch: function(event) {
4533         return event.touches && event.touches.length == 1;
4534     },
4535
4536     /**
4537      * Method: isMultiTouch
4538      * Determine whether event was caused by a multi touch
4539      *
4540      * Parameters:
4541      * event - {Event}
4542      *
4543      * Returns:
4544      * {Boolean}
4545      */
4546     isMultiTouch: function(event) {
4547         return event.touches && event.touches.length > 1;
4548     },
4549
4550     /**
4551      * Method: isTouchEvent
4552      * Determine whether the event was triggered by a touch
4553      * 
4554      * Parameters:
4555      * evt - {Event}
4556      * 
4557      * Returns:
4558      * {Boolean}
4559      */
4560     isTouchEvent: function(evt) {
4561         return ("" + evt.type).indexOf("touch") === 0 || (
4562                 "pointerType" in evt && (
4563                      evt.pointerType === evt.MSPOINTER_TYPE_TOUCH /*IE10 pointer*/ ||
4564                      evt.pointerType === "touch" /*W3C pointer*/));
4565     },
4566
4567     /**
4568      * Method: isLeftClick
4569      * Determine whether event was caused by a left click. 
4570      *
4571      * Parameters:
4572      * event - {Event} 
4573      * 
4574      * Returns:
4575      * {Boolean}
4576      */
4577     isLeftClick: function(event) {
4578         return (((event.which) && (event.which == 1)) ||
4579                 ((event.button) && (event.button == 1)));
4580     },
4581
4582     /**
4583      * Method: isRightClick
4584      * Determine whether event was caused by a right mouse click. 
4585      *
4586      * Parameters:
4587      * event - {Event} 
4588      * 
4589      * Returns:
4590      * {Boolean}
4591      */
4592      isRightClick: function(event) {
4593         return (((event.which) && (event.which == 3)) ||
4594                 ((event.button) && (event.button == 2)));
4595     },
4596      
4597     /**
4598      * Method: stop
4599      * Stops an event from propagating. 
4600      *
4601      * Parameters: 
4602      * event - {Event} 
4603      * allowDefault - {Boolean} If true, we stop the event chain but 
4604      *     still allow the default browser behaviour (text selection,
4605      *     radio-button clicking, etc).  Default is false.
4606      */
4607     stop: function(event, allowDefault) {
4608         
4609         if (!allowDefault) { 
4610             OpenLayers.Event.preventDefault(event);
4611         }
4612                 
4613         if (event.stopPropagation) {
4614             event.stopPropagation();
4615         } else {
4616             event.cancelBubble = true;
4617         }
4618     },
4619
4620     /**
4621      * Method: preventDefault
4622      * Cancels the event if it is cancelable, without stopping further
4623      * propagation of the event.
4624      *
4625      * Parameters:
4626      * event - {Event}
4627      */
4628     preventDefault: function(event) {
4629         if (event.preventDefault) {
4630             event.preventDefault();
4631         } else {
4632             event.returnValue = false;
4633         }
4634     },
4635
4636     /** 
4637      * Method: findElement
4638      * 
4639      * Parameters:
4640      * event - {Event} 
4641      * tagName - {String} 
4642      * 
4643      * Returns:
4644      * {DOMElement} The first node with the given tagName, starting from the
4645      * node the event was triggered on and traversing the DOM upwards
4646      */
4647     findElement: function(event, tagName) {
4648         var element = OpenLayers.Event.element(event);
4649         while (element.parentNode && (!element.tagName ||
4650               (element.tagName.toUpperCase() != tagName.toUpperCase()))){
4651             element = element.parentNode;
4652         }
4653         return element;
4654     },
4655
4656     /** 
4657      * Method: observe
4658      * 
4659      * Parameters:
4660      * elementParam - {DOMElement || String} 
4661      * name - {String} 
4662      * observer - {function} 
4663      * useCapture - {Boolean} 
4664      */
4665     observe: function(elementParam, name, observer, useCapture) {
4666         var element = OpenLayers.Util.getElement(elementParam);
4667         useCapture = useCapture || false;
4668
4669         if (name == 'keypress' &&
4670            (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
4671            || element.attachEvent)) {
4672             name = 'keydown';
4673         }
4674
4675         //if observers cache has not yet been created, create it
4676         if (!this.observers) {
4677             this.observers = {};
4678         }
4679
4680         //if not already assigned, make a new unique cache ID
4681         if (!element._eventCacheID) {
4682             var idPrefix = "eventCacheID_";
4683             if (element.id) {
4684                 idPrefix = element.id + "_" + idPrefix;
4685             }
4686             element._eventCacheID = OpenLayers.Util.createUniqueID(idPrefix);
4687         }
4688
4689         var cacheID = element._eventCacheID;
4690
4691         //if there is not yet a hash entry for this element, add one
4692         if (!this.observers[cacheID]) {
4693             this.observers[cacheID] = [];
4694         }
4695
4696         //add a new observer to this element's list
4697         this.observers[cacheID].push({
4698             'element': element,
4699             'name': name,
4700             'observer': observer,
4701             'useCapture': useCapture
4702         });
4703
4704         //add the actual browser event listener
4705         if (element.addEventListener) {
4706             element.addEventListener(name, observer, useCapture);
4707         } else if (element.attachEvent) {
4708             element.attachEvent('on' + name, observer);
4709         }
4710     },
4711
4712     /** 
4713      * Method: stopObservingElement
4714      * Given the id of an element to stop observing, cycle through the 
4715      *   element's cached observers, calling stopObserving on each one, 
4716      *   skipping those entries which can no longer be removed.
4717      * 
4718      * parameters:
4719      * elementParam - {DOMElement || String} 
4720      */
4721     stopObservingElement: function(elementParam) {
4722         var element = OpenLayers.Util.getElement(elementParam);
4723         var cacheID = element._eventCacheID;
4724
4725         this._removeElementObservers(OpenLayers.Event.observers[cacheID]);
4726     },
4727
4728     /**
4729      * Method: _removeElementObservers
4730      *
4731      * Parameters:
4732      * elementObservers - {Array(Object)} Array of (element, name, 
4733      *                                         observer, usecapture) objects, 
4734      *                                         taken directly from hashtable
4735      */
4736     _removeElementObservers: function(elementObservers) {
4737         if (elementObservers) {
4738             for(var i = elementObservers.length-1; i >= 0; i--) {
4739                 var entry = elementObservers[i];
4740                 OpenLayers.Event.stopObserving.apply(this, [
4741                     entry.element, entry.name, entry.observer, entry.useCapture
4742                 ]);
4743             }
4744         }
4745     },
4746
4747     /**
4748      * Method: stopObserving
4749      * 
4750      * Parameters:
4751      * elementParam - {DOMElement || String} 
4752      * name - {String} 
4753      * observer - {function} 
4754      * useCapture - {Boolean} 
4755      *  
4756      * Returns:
4757      * {Boolean} Whether or not the event observer was removed
4758      */
4759     stopObserving: function(elementParam, name, observer, useCapture) {
4760         useCapture = useCapture || false;
4761     
4762         var element = OpenLayers.Util.getElement(elementParam);
4763         var cacheID = element._eventCacheID;
4764
4765         if (name == 'keypress') {
4766             if ( navigator.appVersion.match(/Konqueror|Safari|KHTML/) || 
4767                  element.detachEvent) {
4768               name = 'keydown';
4769             }
4770         }
4771
4772         // find element's entry in this.observers cache and remove it
4773         var foundEntry = false;
4774         var elementObservers = OpenLayers.Event.observers[cacheID];
4775         if (elementObservers) {
4776     
4777             // find the specific event type in the element's list
4778             var i=0;
4779             while(!foundEntry && i < elementObservers.length) {
4780                 var cacheEntry = elementObservers[i];
4781     
4782                 if ((cacheEntry.name == name) &&
4783                     (cacheEntry.observer == observer) &&
4784                     (cacheEntry.useCapture == useCapture)) {
4785     
4786                     elementObservers.splice(i, 1);
4787                     if (elementObservers.length == 0) {
4788                         delete OpenLayers.Event.observers[cacheID];
4789                     }
4790                     foundEntry = true;
4791                     break; 
4792                 }
4793                 i++;           
4794             }
4795         }
4796     
4797         //actually remove the event listener from browser
4798         if (foundEntry) {
4799             if (element.removeEventListener) {
4800                 element.removeEventListener(name, observer, useCapture);
4801             } else if (element && element.detachEvent) {
4802                 element.detachEvent('on' + name, observer);
4803             }
4804         }
4805         return foundEntry;
4806     },
4807     
4808     /** 
4809      * Method: unloadCache
4810      * Cycle through all the element entries in the events cache and call
4811      *   stopObservingElement on each. 
4812      */
4813     unloadCache: function() {
4814         // check for OpenLayers.Event before checking for observers, because
4815         // OpenLayers.Event may be undefined in IE if no map instance was
4816         // created
4817         if (OpenLayers.Event && OpenLayers.Event.observers) {
4818             for (var cacheID in OpenLayers.Event.observers) {
4819                 var elementObservers = OpenLayers.Event.observers[cacheID];
4820                 OpenLayers.Event._removeElementObservers.apply(this, 
4821                                                            [elementObservers]);
4822             }
4823             OpenLayers.Event.observers = false;
4824         }
4825     },
4826
4827     CLASS_NAME: "OpenLayers.Event"
4828 };
4829
4830 /* prevent memory leaks in IE */
4831 OpenLayers.Event.observe(window, 'unload', OpenLayers.Event.unloadCache, false);
4832
4833 /**
4834  * Class: OpenLayers.Events
4835  */
4836 OpenLayers.Events = OpenLayers.Class({
4837
4838     /** 
4839      * Constant: BROWSER_EVENTS
4840      * {Array(String)} supported events 
4841      */
4842     BROWSER_EVENTS: [
4843         "mouseover", "mouseout",
4844         "mousedown", "mouseup", "mousemove", 
4845         "click", "dblclick", "rightclick", "dblrightclick",
4846         "resize", "focus", "blur",
4847         "touchstart", "touchmove", "touchend",
4848         "keydown"
4849     ],
4850     
4851     /**
4852      * Constant: standard pointer model
4853      * {string}
4854      */
4855     TOUCH_MODEL_POINTER: "pointer",
4856
4857     /**
4858      * Constant: prefixed pointer model (IE10)
4859      * {string}
4860      */
4861     TOUCH_MODEL_MSPOINTER: "MSPointer",
4862
4863     /**
4864      * Constant: legacy touch model
4865      * {string}
4866      */
4867     TOUCH_MODEL_TOUCH: "touch",
4868
4869     /** 
4870      * Property: listeners 
4871      * {Object} Hashtable of Array(Function): events listener functions  
4872      */
4873     listeners: null,
4874
4875     /** 
4876      * Property: object 
4877      * {Object}  the code object issuing application events 
4878      */
4879     object: null,
4880
4881     /** 
4882      * Property: element 
4883      * {DOMElement}  the DOM element receiving browser events 
4884      */
4885     element: null,
4886
4887     /** 
4888      * Property: eventHandler 
4889      * {Function}  bound event handler attached to elements 
4890      */
4891     eventHandler: null,
4892
4893     /** 
4894      * APIProperty: fallThrough 
4895      * {Boolean} 
4896      */
4897     fallThrough: null,
4898
4899     /** 
4900      * APIProperty: includeXY
4901      * {Boolean} Should the .xy property automatically be created for browser
4902      *    mouse events? In general, this should be false. If it is true, then
4903      *    mouse events will automatically generate a '.xy' property on the 
4904      *    event object that is passed. (Prior to OpenLayers 2.7, this was true
4905      *    by default.) Otherwise, you can call the getMousePosition on the
4906      *    relevant events handler on the object available via the 'evt.object'
4907      *    property of the evt object. So, for most events, you can call:
4908      *    function named(evt) { 
4909      *        this.xy = this.object.events.getMousePosition(evt) 
4910      *    } 
4911      *
4912      *    This option typically defaults to false for performance reasons:
4913      *    when creating an events object whose primary purpose is to manage
4914      *    relatively positioned mouse events within a div, it may make
4915      *    sense to set it to true.
4916      *
4917      *    This option is also used to control whether the events object caches
4918      *    offsets. If this is false, it will not: the reason for this is that
4919      *    it is only expected to be called many times if the includeXY property
4920      *    is set to true. If you set this to true, you are expected to clear 
4921      *    the offset cache manually (using this.clearMouseCache()) if:
4922      *        the border of the element changes
4923      *        the location of the element in the page changes
4924     */
4925     includeXY: false,      
4926     
4927     /**
4928      * APIProperty: extensions
4929      * {Object} Event extensions registered with this instance. Keys are
4930      *     event types, values are {OpenLayers.Events.*} extension instances or
4931      *     {Boolean} for events that an instantiated extension provides in
4932      *     addition to the one it was created for.
4933      *
4934      * Extensions create an event in addition to browser events, which usually
4935      * fires when a sequence of browser events is completed. Extensions are
4936      * automatically instantiated when a listener is registered for an event
4937      * provided by an extension.
4938      *
4939      * Extensions are created in the <OpenLayers.Events> namespace using
4940      * <OpenLayers.Class>, and named after the event they provide.
4941      * The constructor receives the target <OpenLayers.Events> instance as
4942      * argument. Extensions that need to capture browser events before they
4943      * propagate can register their listeners events using <register>, with
4944      * {extension: true} as 4th argument.
4945      *
4946      * If an extension creates more than one event, an alias for each event
4947      * type should be created and reference the same class. The constructor
4948      * should set a reference in the target's extensions registry to itself.
4949      *
4950      * Below is a minimal extension that provides the "foostart" and "fooend"
4951      * event types, which replace the native "click" event type if clicked on
4952      * an element with the css class "foo":
4953      *
4954      * (code)
4955      *   OpenLayers.Events.foostart = OpenLayers.Class({
4956      *       initialize: function(target) {
4957      *           this.target = target;
4958      *           this.target.register("click", this, this.doStuff, {extension: true});
4959      *           // only required if extension provides more than one event type
4960      *           this.target.extensions["foostart"] = true;
4961      *           this.target.extensions["fooend"] = true;
4962      *       },
4963      *       destroy: function() {
4964      *           var target = this.target;
4965      *           target.unregister("click", this, this.doStuff);
4966      *           delete this.target;
4967      *           // only required if extension provides more than one event type
4968      *           delete target.extensions["foostart"];
4969      *           delete target.extensions["fooend"];
4970      *       },
4971      *       doStuff: function(evt) {
4972      *           var propagate = true;
4973      *           if (OpenLayers.Event.element(evt).className === "foo") {
4974      *               propagate = false;
4975      *               var target = this.target;
4976      *               target.triggerEvent("foostart");
4977      *               window.setTimeout(function() {
4978      *                   target.triggerEvent("fooend");
4979      *               }, 1000);
4980      *           }
4981      *           return propagate;
4982      *       }
4983      *   });
4984      *   // only required if extension provides more than one event type
4985      *   OpenLayers.Events.fooend = OpenLayers.Events.foostart;
4986      * (end)
4987      * 
4988      */
4989     extensions: null,
4990     
4991     /**
4992      * Property: extensionCount
4993      * {Object} Keys are event types (like in <listeners>), values are the
4994      *     number of extension listeners for each event type.
4995      */
4996     extensionCount: null,
4997
4998     /**
4999      * Method: clearMouseListener
5000      * A version of <clearMouseCache> that is bound to this instance so that
5001      *     it can be used with <OpenLayers.Event.observe> and
5002      *     <OpenLayers.Event.stopObserving>.
5003      */
5004     clearMouseListener: null,
5005
5006     /**
5007      * Constructor: OpenLayers.Events
5008      * Construct an OpenLayers.Events object.
5009      *
5010      * Parameters:
5011      * object - {Object} The js object to which this Events object  is being added
5012      * element - {DOMElement} A dom element to respond to browser events
5013      * eventTypes - {Array(String)} Deprecated.  Array of custom application
5014      *     events.  A listener may be registered for any named event, regardless
5015      *     of the values provided here.
5016      * fallThrough - {Boolean} Allow events to fall through after these have
5017      *                         been handled?
5018      * options - {Object} Options for the events object.
5019      */
5020     initialize: function (object, element, eventTypes, fallThrough, options) {
5021         OpenLayers.Util.extend(this, options);
5022         this.object     = object;
5023         this.fallThrough = fallThrough;
5024         this.listeners  = {};
5025         this.extensions = {};
5026         this.extensionCount = {};
5027         this._pointerTouches = [];
5028         
5029         // if a dom element is specified, add a listeners list 
5030         // for browser events on the element and register them
5031         if (element != null) {
5032             this.attachToElement(element);
5033         }
5034     },
5035
5036     /**
5037      * APIMethod: destroy
5038      */
5039     destroy: function () {
5040         for (var e in this.extensions) {
5041             if (typeof this.extensions[e] !== "boolean") {
5042                 this.extensions[e].destroy();
5043             }
5044         }
5045         this.extensions = null;
5046         if (this.element) {
5047             OpenLayers.Event.stopObservingElement(this.element);
5048             if(this.element.hasScrollEvent) {
5049                 OpenLayers.Event.stopObserving(
5050                     window, "scroll", this.clearMouseListener
5051                 );
5052             }
5053         }
5054         this.element = null;
5055
5056         this.listeners = null;
5057         this.object = null;
5058         this.fallThrough = null;
5059         this.eventHandler = null;
5060     },
5061
5062     /**
5063      * APIMethod: addEventType
5064      * Deprecated.  Any event can be triggered without adding it first.
5065      * 
5066      * Parameters:
5067      * eventName - {String}
5068      */
5069     addEventType: function(eventName) {
5070     },
5071
5072     /**
5073      * Method: attachToElement
5074      *
5075      * Parameters:
5076      * element - {HTMLDOMElement} a DOM element to attach browser events to
5077      */
5078     attachToElement: function (element) {
5079         if (this.element) {
5080             OpenLayers.Event.stopObservingElement(this.element);
5081         } else {
5082             // keep a bound copy of handleBrowserEvent() so that we can
5083             // pass the same function to both Event.observe() and .stopObserving()
5084             this.eventHandler = OpenLayers.Function.bindAsEventListener(
5085                 this.handleBrowserEvent, this
5086             );
5087             
5088             // to be used with observe and stopObserving
5089             this.clearMouseListener = OpenLayers.Function.bind(
5090                 this.clearMouseCache, this
5091             );
5092         }
5093         this.element = element;
5094         var touchModel = this.getTouchModel();
5095         var type;
5096         for (var i = 0, len = this.BROWSER_EVENTS.length; i < len; i++) {
5097             type = this.BROWSER_EVENTS[i];
5098             // register the event cross-browser
5099             OpenLayers.Event.observe(element, type, this.eventHandler
5100             );
5101             if ((touchModel === this.TOUCH_MODEL_POINTER ||
5102                     touchModel === this.TOUCH_MODEL_MSPOINTER) &&
5103                     type.indexOf('touch') === 0) {
5104                 this.addPointerTouchListener(element, type, this.eventHandler);
5105             }
5106         }
5107         // disable dragstart in IE so that mousedown/move/up works normally
5108         OpenLayers.Event.observe(element, "dragstart", OpenLayers.Event.stop);
5109     },
5110     
5111     /**
5112      * APIMethod: on
5113      * Convenience method for registering listeners with a common scope.
5114      *     Internally, this method calls <register> as shown in the examples
5115      *     below.
5116      *
5117      * Example use:
5118      * (code)
5119      * // register a single listener for the "loadstart" event
5120      * events.on({"loadstart": loadStartListener});
5121      *
5122      * // this is equivalent to the following
5123      * events.register("loadstart", undefined, loadStartListener);
5124      *
5125      * // register multiple listeners to be called with the same `this` object
5126      * events.on({
5127      *     "loadstart": loadStartListener,
5128      *     "loadend": loadEndListener,
5129      *     scope: object
5130      * });
5131      *
5132      * // this is equivalent to the following
5133      * events.register("loadstart", object, loadStartListener);
5134      * events.register("loadend", object, loadEndListener);
5135      * (end)
5136      *
5137      * Parameters:
5138      *  object - {Object}     
5139      */
5140     on: function(object) {
5141         for(var type in object) {
5142             if(type != "scope" && object.hasOwnProperty(type)) {
5143                 this.register(type, object.scope, object[type]);
5144             }
5145         }
5146     },
5147
5148     /**
5149      * APIMethod: register
5150      * Register an event on the events object.
5151      *
5152      * When the event is triggered, the 'func' function will be called, in the
5153      * context of 'obj'. Imagine we were to register an event, specifying an 
5154      * OpenLayers.Bounds Object as 'obj'. When the event is triggered, the 
5155      * context in the callback function will be our Bounds object. This means
5156      * that within our callback function, we can access the properties and 
5157      * methods of the Bounds object through the "this" variable. So our 
5158      * callback could execute something like: 
5159      * :    leftStr = "Left: " + this.left;
5160      *   
5161      *                   or
5162      *  
5163      * :    centerStr = "Center: " + this.getCenterLonLat();
5164      *
5165      * Parameters:
5166      * type - {String} Name of the event to register
5167      * obj - {Object} The object to bind the context to for the callback#.
5168      *     If no object is specified, default is the Events's 'object' property.
5169      * func - {Function} The callback function. If no callback is 
5170      *     specified, this function does nothing.
5171      * priority - {Boolean|Object} If true, adds the new listener to the
5172      *     *front* of the events queue instead of to the end.
5173      *
5174      * Valid options for priority:
5175      * extension - {Boolean} If true, then the event will be registered as
5176      *     extension event. Extension events are handled before all other
5177      *     events.
5178      */
5179     register: function (type, obj, func, priority) {
5180         if (type in OpenLayers.Events && !this.extensions[type]) {
5181             this.extensions[type] = new OpenLayers.Events[type](this);
5182         }
5183         if (func != null) {
5184             if (obj == null)  {
5185                 obj = this.object;
5186             }
5187             var listeners = this.listeners[type];
5188             if (!listeners) {
5189                 listeners = [];
5190                 this.listeners[type] = listeners;
5191                 this.extensionCount[type] = 0;
5192             }
5193             var listener = {obj: obj, func: func};
5194             if (priority) {
5195                 listeners.splice(this.extensionCount[type], 0, listener);
5196                 if (typeof priority === "object" && priority.extension) {
5197                     this.extensionCount[type]++;
5198                 }
5199             } else {
5200                 listeners.push(listener);
5201             }
5202         }
5203     },
5204
5205     /**
5206      * APIMethod: registerPriority
5207      * Same as register() but adds the new listener to the *front* of the
5208      *     events queue instead of to the end.
5209      *    
5210      *     TODO: get rid of this in 3.0 - Decide whether listeners should be 
5211      *     called in the order they were registered or in reverse order.
5212      *
5213      *
5214      * Parameters:
5215      * type - {String} Name of the event to register
5216      * obj - {Object} The object to bind the context to for the callback#.
5217      *                If no object is specified, default is the Events's 
5218      *                'object' property.
5219      * func - {Function} The callback function. If no callback is 
5220      *                   specified, this function does nothing.
5221      */
5222     registerPriority: function (type, obj, func) {
5223         this.register(type, obj, func, true);
5224     },
5225     
5226     /**
5227      * APIMethod: un
5228      * Convenience method for unregistering listeners with a common scope.
5229      *     Internally, this method calls <unregister> as shown in the examples
5230      *     below.
5231      *
5232      * Example use:
5233      * (code)
5234      * // unregister a single listener for the "loadstart" event
5235      * events.un({"loadstart": loadStartListener});
5236      *
5237      * // this is equivalent to the following
5238      * events.unregister("loadstart", undefined, loadStartListener);
5239      *
5240      * // unregister multiple listeners with the same `this` object
5241      * events.un({
5242      *     "loadstart": loadStartListener,
5243      *     "loadend": loadEndListener,
5244      *     scope: object
5245      * });
5246      *
5247      * // this is equivalent to the following
5248      * events.unregister("loadstart", object, loadStartListener);
5249      * events.unregister("loadend", object, loadEndListener);
5250      * (end)
5251      */
5252     un: function(object) {
5253         for(var type in object) {
5254             if(type != "scope" && object.hasOwnProperty(type)) {
5255                 this.unregister(type, object.scope, object[type]);
5256             }
5257         }
5258     },
5259
5260     /**
5261      * APIMethod: unregister
5262      *
5263      * Parameters:
5264      * type - {String} 
5265      * obj - {Object} If none specified, defaults to this.object
5266      * func - {Function} 
5267      */
5268     unregister: function (type, obj, func) {
5269         if (obj == null)  {
5270             obj = this.object;
5271         }
5272         var listeners = this.listeners[type];
5273         if (listeners != null) {
5274             for (var i=0, len=listeners.length; i<len; i++) {
5275                 if (listeners[i].obj == obj && listeners[i].func == func) {
5276                     listeners.splice(i, 1);
5277                     break;
5278                 }
5279             }
5280         }
5281     },
5282
5283     /** 
5284      * Method: remove
5285      * Remove all listeners for a given event type. If type is not registered,
5286      *     does nothing.
5287      *
5288      * Parameters:
5289      * type - {String} 
5290      */
5291     remove: function(type) {
5292         if (this.listeners[type] != null) {
5293             this.listeners[type] = [];
5294         }
5295     },
5296
5297     /**
5298      * APIMethod: triggerEvent
5299      * Trigger a specified registered event.  
5300      * 
5301      * Parameters:
5302      * type - {String} 
5303      * evt - {Event || Object} will be passed to the listeners.
5304      *
5305      * Returns:
5306      * {Boolean} The last listener return.  If a listener returns false, the
5307      *     chain of listeners will stop getting called.
5308      */
5309     triggerEvent: function (type, evt) {
5310         var listeners = this.listeners[type];
5311
5312         // fast path
5313         if(!listeners || listeners.length == 0) {
5314             return undefined;
5315         }
5316
5317         // prep evt object with object & div references
5318         if (evt == null) {
5319             evt = {};
5320         }
5321         evt.object = this.object;
5322         evt.element = this.element;
5323         if(!evt.type) {
5324             evt.type = type;
5325         }
5326     
5327         // execute all callbacks registered for specified type
5328         // get a clone of the listeners array to
5329         // allow for splicing during callbacks
5330         listeners = listeners.slice();
5331         var continueChain;
5332         for (var i=0, len=listeners.length; i<len; i++) {
5333             var callback = listeners[i];
5334             // bind the context to callback.obj
5335             continueChain = callback.func.apply(callback.obj, [evt]);
5336
5337             if ((continueChain != undefined) && (continueChain == false)) {
5338                 // if callback returns false, execute no more callbacks.
5339                 break;
5340             }
5341         }
5342         // don't fall through to other DOM elements
5343         if (!this.fallThrough) {           
5344             OpenLayers.Event.stop(evt, true);
5345         }
5346         return continueChain;
5347     },
5348
5349     /**
5350      * Method: handleBrowserEvent
5351      * Basically just a wrapper to the triggerEvent() function, but takes 
5352      *     care to set a property 'xy' on the event with the current mouse 
5353      *     position.
5354      *
5355      * Parameters:
5356      * evt - {Event} 
5357      */
5358     handleBrowserEvent: function (evt) {
5359         var type = evt.type, listeners = this.listeners[type];
5360         if(!listeners || listeners.length == 0) {
5361             // noone's listening, bail out
5362             return;
5363         }
5364         // add clientX & clientY to all events - corresponds to average x, y
5365         var touches = evt.touches;
5366         if (touches && touches[0]) {
5367             var x = 0;
5368             var y = 0;
5369             var num = touches.length;
5370             var touch;
5371             for (var i=0; i<num; ++i) {
5372                 touch = this.getTouchClientXY(touches[i]);
5373                 x += touch.clientX;
5374                 y += touch.clientY;
5375             }
5376             evt.clientX = x / num;
5377             evt.clientY = y / num;
5378         }
5379         if (this.includeXY) {
5380             evt.xy = this.getMousePosition(evt);
5381         } 
5382         this.triggerEvent(type, evt);
5383     },
5384     
5385     /**
5386      * Method: getTouchClientXY
5387      * WebKit has a few bugs for clientX/clientY. This method detects them
5388      * and calculate the correct values.
5389      *
5390      * Parameters:
5391      * evt - {Touch} a Touch object from a TouchEvent
5392      * 
5393      * Returns:
5394      * {Object} An object with only clientX and clientY properties with the
5395      * calculated values.
5396      */
5397     getTouchClientXY: function (evt) {
5398         // olMochWin is to override window, used for testing
5399         var win = window.olMockWin || window,
5400             winPageX = win.pageXOffset,
5401             winPageY = win.pageYOffset,
5402             x = evt.clientX,
5403             y = evt.clientY;
5404         
5405         if (evt.pageY === 0 && Math.floor(y) > Math.floor(evt.pageY) ||
5406             evt.pageX === 0 && Math.floor(x) > Math.floor(evt.pageX)) {
5407             // iOS4 include scroll offset in clientX/Y
5408             x = x - winPageX;
5409             y = y - winPageY;
5410         } else if (y < (evt.pageY - winPageY) || x < (evt.pageX - winPageX) ) {
5411             // Some Android browsers have totally bogus values for clientX/Y
5412             // when scrolling/zooming a page
5413             x = evt.pageX - winPageX;
5414             y = evt.pageY - winPageY;
5415         }
5416         
5417         evt.olClientX = x;
5418         evt.olClientY = y;
5419         
5420         return {
5421             clientX: x,
5422             clientY: y
5423         };
5424     },
5425     
5426     /**
5427      * APIMethod: clearMouseCache
5428      * Clear cached data about the mouse position. This should be called any 
5429      *     time the element that events are registered on changes position 
5430      *     within the page.
5431      */
5432     clearMouseCache: function() { 
5433         this.element.scrolls = null;
5434         this.element.lefttop = null;
5435         this.element.offsets = null;
5436     },      
5437
5438     /**
5439      * Method: getMousePosition
5440      * 
5441      * Parameters:
5442      * evt - {Event} 
5443      * 
5444      * Returns:
5445      * {<OpenLayers.Pixel>} The current xy coordinate of the mouse, adjusted
5446      *                      for offsets
5447      */
5448     getMousePosition: function (evt) {
5449         if (!this.includeXY) {
5450             this.clearMouseCache();
5451         } else if (!this.element.hasScrollEvent) {
5452             OpenLayers.Event.observe(window, "scroll", this.clearMouseListener);
5453             this.element.hasScrollEvent = true;
5454         }
5455         
5456         if (!this.element.scrolls) {
5457             var viewportElement = OpenLayers.Util.getViewportElement();
5458             this.element.scrolls = [
5459                 window.pageXOffset || viewportElement.scrollLeft,
5460                 window.pageYOffset || viewportElement.scrollTop
5461             ];
5462         }
5463
5464         if (!this.element.lefttop) {
5465             this.element.lefttop = [
5466                 (document.documentElement.clientLeft || 0),
5467                 (document.documentElement.clientTop  || 0)
5468             ];
5469         }
5470         
5471         if (!this.element.offsets) {
5472             this.element.offsets = OpenLayers.Util.pagePosition(this.element);
5473         }
5474
5475         return new OpenLayers.Pixel(
5476             (evt.clientX + this.element.scrolls[0]) - this.element.offsets[0]
5477                          - this.element.lefttop[0], 
5478             (evt.clientY + this.element.scrolls[1]) - this.element.offsets[1]
5479                          - this.element.lefttop[1]
5480         ); 
5481     },
5482
5483     /**
5484      * Method: getTouchModel
5485      * Get the touch model currently in use.
5486      * 
5487      * This is cached on OpenLayers.Events as _TOUCH_MODEL 
5488      * 
5489      * Returns:
5490      * {string} The current touch model (TOUCH_MODEL_xxx), null if none
5491      */
5492     getTouchModel: function() {
5493         if (!("_TOUCH_MODEL" in OpenLayers.Events)) {
5494             OpenLayers.Events._TOUCH_MODEL =
5495                     (window.PointerEvent && "pointer") ||
5496                     (window.MSPointerEvent && "MSPointer") ||
5497                     (("ontouchdown" in document) && "touch") ||
5498                     null;
5499         }
5500         return OpenLayers.Events._TOUCH_MODEL;
5501     },
5502
5503     /**
5504      * Method: addPointerTouchListener
5505      *
5506      * Parameters:
5507      * element - {DOMElement} The DOM element to register the listener on
5508      * type - {String} The event type
5509      * handler - {Function} the handler
5510      */
5511     addPointerTouchListener: function (element, type, handler) {
5512         var eventHandler = this.eventHandler;
5513         var touches = this._pointerTouches;
5514
5515         function pointerHandler(evt) {
5516             handler(OpenLayers.Util.applyDefaults({
5517                 stopPropagation: function() {
5518                     for (var i=touches.length-1; i>=0; --i) {
5519                         touches[i].stopPropagation();
5520                     }
5521                 },
5522                 preventDefault: function() {
5523                     for (var i=touches.length-1; i>=0; --i) {
5524                         touches[i].preventDefault();
5525                     }
5526                 },
5527                 type: type
5528             }, evt));
5529         }
5530
5531         switch (type) {
5532             case 'touchstart':
5533                 return this.addPointerTouchListenerStart(element, type, pointerHandler);
5534             case 'touchend':
5535                 return this.addPointerTouchListenerEnd(element, type, pointerHandler);
5536             case 'touchmove':
5537                 return this.addPointerTouchListenerMove(element, type, pointerHandler);
5538             default:
5539                 throw 'Unknown touch event type';
5540         }
5541     },
5542
5543     /**
5544      * Method: addPointerTouchListenerStart
5545      *
5546      * Parameters:
5547      * element - {DOMElement} The DOM element to register the listener on
5548      * type - {String} The event type
5549      * handler - {Function} the handler
5550      */
5551     addPointerTouchListenerStart: function(element, type, handler) {
5552         var touches = this._pointerTouches;
5553
5554         var cb = function(e) {
5555
5556             // pointer could be mouse or pen
5557             if (!OpenLayers.Event.isTouchEvent(e)) {
5558                 return;
5559             }
5560
5561             var alreadyInArray = false;
5562             for (var i=0, ii=touches.length; i<ii; ++i) {
5563                 if (touches[i].pointerId == e.pointerId) {
5564                     alreadyInArray = true;
5565                     break;
5566                 }
5567             }
5568             if (!alreadyInArray) {
5569                 touches.push(e);
5570             }
5571
5572             e.touches = touches.slice();
5573             handler(e);
5574         };
5575
5576         OpenLayers.Event.observe(element,
5577                 this.getTouchModel() === this.TOUCH_MODEL_MSPOINTER ?
5578                         'MSPointerDown' : 'pointerdown',
5579                 cb);
5580         
5581         // the pointerId only needs to be removed from the _pointerTouches array
5582         // when the pointer has left its element
5583         var internalCb = function (e) {
5584
5585             // pointer could be mouse or pen
5586             if (!OpenLayers.Event.isTouchEvent(e)) {
5587                 return;
5588             }
5589
5590             var up = false;
5591             for (var i = 0, ii = touches.length; i < ii; ++i) {
5592                 if (touches[i].pointerId == e.pointerId) {
5593                     if (this.clientWidth != 0 && this.clientHeight != 0) {
5594                         if ((Math.ceil(e.clientX) >= this.clientWidth || Math.ceil(e.clientY) >= this.clientHeight)) {
5595                             touches.splice(i, 1);
5596                         }
5597                     }
5598                     break;
5599                 }
5600             }
5601         };
5602         OpenLayers.Event.observe(element,
5603                 this.getTouchModel() === this.TOUCH_MODEL_MSPOINTER ?
5604                         'MSPointerOut' : 'pointerout',
5605                 internalCb);
5606     },
5607
5608     /**
5609      * Method: addPointerTouchListenerMove
5610      *
5611      * Parameters:
5612      * element - {DOMElement} The DOM element to register the listener on
5613      * type - {String} The event type
5614      * handler - {Function} the handler
5615      */
5616     addPointerTouchListenerMove: function (element, type, handler) {
5617         var touches = this._pointerTouches;
5618         var cb = function(e) {
5619
5620             // pointer could be mouse or pen
5621             if (!OpenLayers.Event.isTouchEvent(e)) {
5622                 return;
5623             }
5624
5625             if (touches.length == 1 && touches[0].pageX == e.pageX &&
5626                     touches[0].pageY == e.pageY) {
5627                 // don't trigger event when pointer has not moved
5628                 return;
5629             }
5630             for (var i=0, ii=touches.length; i<ii; ++i) {
5631                 if (touches[i].pointerId == e.pointerId) {
5632                     touches[i] = e;
5633                     break;
5634                 }
5635             }
5636
5637             e.touches = touches.slice();
5638             handler(e);
5639         };
5640
5641         OpenLayers.Event.observe(element,
5642                 this.getTouchModel() === this.TOUCH_MODEL_MSPOINTER ?
5643                         'MSPointerMove' : 'pointermove',
5644                 cb);
5645     },
5646
5647     /**
5648      * Method: addPointerTouchListenerEnd
5649      *
5650      * Parameters:
5651      * element - {DOMElement} The DOM element to register the listener on
5652      * type - {String} The event type
5653      * handler - {Function} the handler
5654      */
5655     addPointerTouchListenerEnd: function (element, type, handler) {
5656         var touches = this._pointerTouches;
5657
5658         var cb = function(e) {
5659
5660             // pointer could be mouse or pen
5661             if (!OpenLayers.Event.isTouchEvent(e)) {
5662                 return;
5663             }
5664
5665             for (var i=0, ii=touches.length; i<ii; ++i) {
5666                 if (touches[i].pointerId == e.pointerId) {
5667                     touches.splice(i, 1);
5668                     break;
5669                 }
5670             }
5671             
5672             e.touches = touches.slice();
5673             handler(e);
5674         };
5675
5676         OpenLayers.Event.observe(element,
5677                 this.getTouchModel() === this.TOUCH_MODEL_MSPOINTER ?
5678                         'MSPointerUp' : 'pointerup',
5679                 cb);
5680     },
5681
5682     CLASS_NAME: "OpenLayers.Events"
5683 });
5684 /* ======================================================================
5685     OpenLayers/Icon.js
5686    ====================================================================== */
5687
5688 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
5689  * full list of contributors). Published under the 2-clause BSD license.
5690  * See license.txt in the OpenLayers distribution or repository for the
5691  * full text of the license. */
5692
5693 /**
5694  * @requires OpenLayers/BaseTypes/Class.js
5695  */
5696
5697 /**
5698  * Class: OpenLayers.Icon
5699  * 
5700  * The icon represents a graphical icon on the screen.  Typically used in
5701  * conjunction with a <OpenLayers.Marker> to represent markers on a screen.
5702  *
5703  * An icon has a url, size and position.  It also contains an offset which 
5704  * allows the center point to be represented correctly.  This can be
5705  * provided either as a fixed offset or a function provided to calculate
5706  * the desired offset. 
5707  * 
5708  */
5709 OpenLayers.Icon = OpenLayers.Class({
5710     
5711     /** 
5712      * Property: url 
5713      * {String}  image url
5714      */
5715     url: null,
5716     
5717     /** 
5718      * Property: size 
5719      * {<OpenLayers.Size>|Object} An OpenLayers.Size or
5720      * an object with a 'w' and 'h' properties.
5721      */
5722     size: null,
5723
5724     /** 
5725      * Property: offset 
5726      * {<OpenLayers.Pixel>|Object} distance in pixels to offset the
5727      * image when being rendered. An OpenLayers.Pixel or an object
5728      * with a 'x' and 'y' properties.
5729      */
5730     offset: null,    
5731     
5732     /** 
5733      * Property: calculateOffset 
5734      * {Function} Function to calculate the offset (based on the size)
5735      */
5736     calculateOffset: null,    
5737     
5738     /** 
5739      * Property: imageDiv 
5740      * {DOMElement} 
5741      */
5742     imageDiv: null,
5743
5744     /** 
5745      * Property: px 
5746      * {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object
5747      * with a 'x' and 'y' properties.
5748      */
5749     px: null,
5750     
5751     /** 
5752      * Constructor: OpenLayers.Icon
5753      * Creates an icon, which is an image tag in a div.  
5754      *
5755      * url - {String} 
5756      * size - {<OpenLayers.Size>|Object} An OpenLayers.Size or an
5757      *                                   object with a 'w' and 'h'
5758      *                                   properties.
5759      * offset - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an
5760      *                                      object with a 'x' and 'y'
5761      *                                      properties.
5762      * calculateOffset - {Function} 
5763      */
5764     initialize: function(url, size, offset, calculateOffset) {
5765         this.url = url;
5766         this.size = size || {w: 20, h: 20};
5767         this.offset = offset || {x: -(this.size.w/2), y: -(this.size.h/2)};
5768         this.calculateOffset = calculateOffset;
5769
5770         var id = OpenLayers.Util.createUniqueID("OL_Icon_");
5771         this.imageDiv = OpenLayers.Util.createAlphaImageDiv(id);
5772     },
5773     
5774     /** 
5775      * Method: destroy
5776      * Nullify references and remove event listeners to prevent circular 
5777      * references and memory leaks
5778      */
5779     destroy: function() {
5780         // erase any drawn elements
5781         this.erase();
5782
5783         OpenLayers.Event.stopObservingElement(this.imageDiv.firstChild); 
5784         this.imageDiv.innerHTML = "";
5785         this.imageDiv = null;
5786     },
5787
5788     /** 
5789      * Method: clone
5790      * 
5791      * Returns:
5792      * {<OpenLayers.Icon>} A fresh copy of the icon.
5793      */
5794     clone: function() {
5795         return new OpenLayers.Icon(this.url, 
5796                                    this.size, 
5797                                    this.offset, 
5798                                    this.calculateOffset);
5799     },
5800     
5801     /**
5802      * Method: setSize
5803      * 
5804      * Parameters:
5805      * size - {<OpenLayers.Size>|Object} An OpenLayers.Size or
5806      * an object with a 'w' and 'h' properties.
5807      */
5808     setSize: function(size) {
5809         if (size != null) {
5810             this.size = size;
5811         }
5812         this.draw();
5813     },
5814     
5815     /**
5816      * Method: setUrl
5817      * 
5818      * Parameters:
5819      * url - {String} 
5820      */
5821     setUrl: function(url) {
5822         if (url != null) {
5823             this.url = url;
5824         }
5825         this.draw();
5826     },
5827
5828     /** 
5829      * Method: draw
5830      * Move the div to the given pixel.
5831      * 
5832      * Parameters:
5833      * px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an
5834      *                                  object with a 'x' and 'y' properties.
5835      * 
5836      * Returns:
5837      * {DOMElement} A new DOM Image of this icon set at the location passed-in
5838      */
5839     draw: function(px) {
5840         OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, 
5841                                             null, 
5842                                             null, 
5843                                             this.size, 
5844                                             this.url, 
5845                                             "absolute");
5846         this.moveTo(px);
5847         return this.imageDiv;
5848     }, 
5849
5850     /** 
5851      * Method: erase
5852      * Erase the underlying image element.
5853      */
5854     erase: function() {
5855         if (this.imageDiv != null && this.imageDiv.parentNode != null) {
5856             OpenLayers.Element.remove(this.imageDiv);
5857         }
5858     }, 
5859     
5860     /** 
5861      * Method: setOpacity
5862      * Change the icon's opacity
5863      *
5864      * Parameters:
5865      * opacity - {float} 
5866      */
5867     setOpacity: function(opacity) {
5868         OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, null, null, null, 
5869                                             null, null, null, null, opacity);
5870
5871     },
5872     
5873     /**
5874      * Method: moveTo
5875      * move icon to passed in px.
5876      *
5877      * Parameters:
5878      * px - {<OpenLayers.Pixel>|Object} the pixel position to move to.
5879      * An OpenLayers.Pixel or an object with a 'x' and 'y' properties.
5880      */
5881     moveTo: function (px) {
5882         //if no px passed in, use stored location
5883         if (px != null) {
5884             this.px = px;
5885         }
5886
5887         if (this.imageDiv != null) {
5888             if (this.px == null) {
5889                 this.display(false);
5890             } else {
5891                 if (this.calculateOffset) {
5892                     this.offset = this.calculateOffset(this.size);  
5893                 }
5894                 OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, null, {
5895                     x: this.px.x + this.offset.x,
5896                     y: this.px.y + this.offset.y
5897                 });
5898             }
5899         }
5900     },
5901     
5902     /** 
5903      * Method: display
5904      * Hide or show the icon
5905      *
5906      * Parameters:
5907      * display - {Boolean} 
5908      */
5909     display: function(display) {
5910         this.imageDiv.style.display = (display) ? "" : "none"; 
5911     },
5912     
5913
5914     /**
5915      * APIMethod: isDrawn
5916      * 
5917      * Returns:
5918      * {Boolean} Whether or not the icon is drawn.
5919      */
5920     isDrawn: function() {
5921         // nodeType 11 for ie, whose nodes *always* have a parentNode
5922         // (of type document fragment)
5923         var isDrawn = (this.imageDiv && this.imageDiv.parentNode && 
5924                        (this.imageDiv.parentNode.nodeType != 11));    
5925
5926         return isDrawn;   
5927     },
5928
5929     CLASS_NAME: "OpenLayers.Icon"
5930 });
5931 /* ======================================================================
5932     OpenLayers/Marker.js
5933    ====================================================================== */
5934
5935 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
5936  * full list of contributors). Published under the 2-clause BSD license.
5937  * See license.txt in the OpenLayers distribution or repository for the
5938  * full text of the license. */
5939
5940
5941 /**
5942  * @requires OpenLayers/BaseTypes/Class.js
5943  * @requires OpenLayers/Events.js
5944  * @requires OpenLayers/Icon.js
5945  */
5946
5947 /**
5948  * Class: OpenLayers.Marker
5949  * Instances of OpenLayers.Marker are a combination of a 
5950  * <OpenLayers.LonLat> and an <OpenLayers.Icon>.  
5951  *
5952  * Markers are generally added to a special layer called
5953  * <OpenLayers.Layer.Markers>.
5954  *
5955  * Example:
5956  * (code)
5957  * var markers = new OpenLayers.Layer.Markers( "Markers" );
5958  * map.addLayer(markers);
5959  *
5960  * var size = new OpenLayers.Size(21,25);
5961  * var offset = new OpenLayers.Pixel(-(size.w/2), -size.h);
5962  * var icon = new OpenLayers.Icon('http://www.openlayers.org/dev/img/marker.png', size, offset);
5963  * markers.addMarker(new OpenLayers.Marker(new OpenLayers.LonLat(0,0),icon));
5964  * markers.addMarker(new OpenLayers.Marker(new OpenLayers.LonLat(0,0),icon.clone()));
5965  *
5966  * (end)
5967  *
5968  * Note that if you pass an icon into the Marker constructor, it will take
5969  * that icon and use it. This means that you should not share icons between
5970  * markers -- you use them once, but you should clone() for any additional
5971  * markers using that same icon.
5972  */
5973 OpenLayers.Marker = OpenLayers.Class({
5974     
5975     /** 
5976      * Property: icon 
5977      * {<OpenLayers.Icon>} The icon used by this marker.
5978      */
5979     icon: null,
5980
5981     /** 
5982      * Property: lonlat 
5983      * {<OpenLayers.LonLat>} location of object
5984      */
5985     lonlat: null,
5986     
5987     /** 
5988      * Property: events 
5989      * {<OpenLayers.Events>} the event handler.
5990      */
5991     events: null,
5992     
5993     /** 
5994      * Property: map 
5995      * {<OpenLayers.Map>} the map this marker is attached to
5996      */
5997     map: null,
5998     
5999     /** 
6000      * Constructor: OpenLayers.Marker
6001      *
6002      * Parameters:
6003      * lonlat - {<OpenLayers.LonLat>} the position of this marker
6004      * icon - {<OpenLayers.Icon>}  the icon for this marker
6005      */
6006     initialize: function(lonlat, icon) {
6007         this.lonlat = lonlat;
6008         
6009         var newIcon = (icon) ? icon : OpenLayers.Marker.defaultIcon();
6010         if (this.icon == null) {
6011             this.icon = newIcon;
6012         } else {
6013             this.icon.url = newIcon.url;
6014             this.icon.size = newIcon.size;
6015             this.icon.offset = newIcon.offset;
6016             this.icon.calculateOffset = newIcon.calculateOffset;
6017         }
6018         this.events = new OpenLayers.Events(this, this.icon.imageDiv);
6019     },
6020     
6021     /**
6022      * APIMethod: destroy
6023      * Destroy the marker. You must first remove the marker from any 
6024      * layer which it has been added to, or you will get buggy behavior.
6025      * (This can not be done within the marker since the marker does not
6026      * know which layer it is attached to.)
6027      */
6028     destroy: function() {
6029         // erase any drawn features
6030         this.erase();
6031
6032         this.map = null;
6033
6034         this.events.destroy();
6035         this.events = null;
6036
6037         if (this.icon != null) {
6038             this.icon.destroy();
6039             this.icon = null;
6040         }
6041     },
6042     
6043     /** 
6044     * Method: draw
6045     * Calls draw on the icon, and returns that output.
6046     * 
6047     * Parameters:
6048     * px - {<OpenLayers.Pixel>}
6049     * 
6050     * Returns:
6051     * {DOMElement} A new DOM Image with this marker's icon set at the 
6052     * location passed-in
6053     */
6054     draw: function(px) {
6055         return this.icon.draw(px);
6056     }, 
6057
6058     /** 
6059     * Method: erase
6060     * Erases any drawn elements for this marker.
6061     */
6062     erase: function() {
6063         if (this.icon != null) {
6064             this.icon.erase();
6065         }
6066     }, 
6067
6068     /**
6069     * Method: moveTo
6070     * Move the marker to the new location.
6071     *
6072     * Parameters:
6073     * px - {<OpenLayers.Pixel>|Object} the pixel position to move to.
6074     * An OpenLayers.Pixel or an object with a 'x' and 'y' properties.
6075     */
6076     moveTo: function (px) {
6077         if ((px != null) && (this.icon != null)) {
6078             this.icon.moveTo(px);
6079         }           
6080         this.lonlat = this.map.getLonLatFromLayerPx(px);
6081     },
6082
6083     /**
6084      * APIMethod: isDrawn
6085      * 
6086      * Returns:
6087      * {Boolean} Whether or not the marker is drawn.
6088      */
6089     isDrawn: function() {
6090         var isDrawn = (this.icon && this.icon.isDrawn());
6091         return isDrawn;   
6092     },
6093
6094     /**
6095      * Method: onScreen
6096      *
6097      * Returns:
6098      * {Boolean} Whether or not the marker is currently visible on screen.
6099      */
6100     onScreen:function() {
6101         
6102         var onScreen = false;
6103         if (this.map) {
6104             var screenBounds = this.map.getExtent();
6105             onScreen = screenBounds.containsLonLat(this.lonlat);
6106         }    
6107         return onScreen;
6108     },
6109     
6110     /**
6111      * Method: inflate
6112      * Englarges the markers icon by the specified ratio.
6113      *
6114      * Parameters:
6115      * inflate - {float} the ratio to enlarge the marker by (passing 2
6116      *                   will double the size).
6117      */
6118     inflate: function(inflate) {
6119         if (this.icon) {
6120             this.icon.setSize({
6121                 w: this.icon.size.w * inflate,
6122                 h: this.icon.size.h * inflate
6123             });
6124         }        
6125     },
6126     
6127     /** 
6128      * Method: setOpacity
6129      * Change the opacity of the marker by changin the opacity of 
6130      *   its icon
6131      * 
6132      * Parameters:
6133      * opacity - {float}  Specified as fraction (0.4, etc)
6134      */
6135     setOpacity: function(opacity) {
6136         this.icon.setOpacity(opacity);
6137     },
6138
6139     /**
6140      * Method: setUrl
6141      * Change URL of the Icon Image.
6142      * 
6143      * url - {String} 
6144      */
6145     setUrl: function(url) {
6146         this.icon.setUrl(url);
6147     },    
6148
6149     /** 
6150      * Method: display
6151      * Hide or show the icon
6152      * 
6153      * display - {Boolean} 
6154      */
6155     display: function(display) {
6156         this.icon.display(display);
6157     },
6158
6159     CLASS_NAME: "OpenLayers.Marker"
6160 });
6161
6162
6163 /**
6164  * Function: defaultIcon
6165  * Creates a default <OpenLayers.Icon>.
6166  * 
6167  * Returns:
6168  * {<OpenLayers.Icon>} A default OpenLayers.Icon to use for a marker
6169  */
6170 OpenLayers.Marker.defaultIcon = function() {
6171     return new OpenLayers.Icon(OpenLayers.Util.getImageLocation("marker.png"),
6172                                {w: 21, h: 25}, {x: -10.5, y: -25});
6173 };
6174     
6175
6176 /* ======================================================================
6177     OpenLayers/Util/vendorPrefix.js
6178    ====================================================================== */
6179
6180 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
6181  * full list of contributors). Published under the 2-clause BSD license.
6182  * See license.txt in the OpenLayers distribution or repository for the
6183  * full text of the license. */
6184
6185 /**
6186  * @requires OpenLayers/SingleFile.js
6187  */
6188
6189 OpenLayers.Util = OpenLayers.Util || {};
6190 /**
6191  * Namespace: OpenLayers.Util.vendorPrefix
6192  * A collection of utility functions to detect vendor prefixed features
6193  */
6194 OpenLayers.Util.vendorPrefix = (function() {
6195     "use strict";
6196     
6197     var VENDOR_PREFIXES = ["", "O", "ms", "Moz", "Webkit"],
6198         divStyle = document.createElement("div").style,
6199         cssCache = {},
6200         jsCache = {};
6201
6202     
6203     /**
6204      * Function: domToCss
6205      * Converts a upper camel case DOM style property name to a CSS property
6206      *      i.e. transformOrigin -> transform-origin
6207      *      or   WebkitTransformOrigin -> -webkit-transform-origin
6208      *
6209      * Parameters:
6210      * prefixedDom - {String} The property to convert
6211      *
6212      * Returns:
6213      * {String} The CSS property
6214      */
6215     function domToCss(prefixedDom) {
6216         if (!prefixedDom) { return null; }
6217         return prefixedDom.
6218             replace(/([A-Z])/g, function(c) { return "-" + c.toLowerCase(); }).
6219             replace(/^ms-/, "-ms-");
6220     }
6221
6222     /**
6223      * APIMethod: css
6224      * Detect which property is used for a CSS property
6225      *
6226      * Parameters:
6227      * property - {String} The standard (unprefixed) CSS property name
6228      *
6229      * Returns:
6230      * {String} The standard CSS property, prefixed property or null if not
6231      *          supported
6232      */
6233     function css(property) {
6234         if (cssCache[property] === undefined) {
6235             var domProperty = property.
6236                 replace(/(-[\s\S])/g, function(c) { return c.charAt(1).toUpperCase(); });
6237             var prefixedDom = style(domProperty);
6238             cssCache[property] = domToCss(prefixedDom);
6239         }
6240         return cssCache[property];
6241     }
6242
6243     /**
6244      * APIMethod: js
6245      * Detect which property is used for a JS property/method
6246      *
6247      * Parameters:
6248      * obj - {Object} The object to test on
6249      * property - {String} The standard (unprefixed) JS property name
6250      *
6251      * Returns:
6252      * {String} The standard JS property, prefixed property or null if not
6253      *          supported
6254      */
6255     function js(obj, property) {
6256         if (jsCache[property] === undefined) {
6257             var tmpProp,
6258                 i = 0,
6259                 l = VENDOR_PREFIXES.length,
6260                 prefix,
6261                 isStyleObj = (typeof obj.cssText !== "undefined");
6262
6263             jsCache[property] = null;
6264             for(; i<l; i++) {
6265                 prefix = VENDOR_PREFIXES[i];
6266                 if(prefix) {
6267                     if (!isStyleObj) {
6268                         // js prefix should be lower-case, while style
6269                         // properties have upper case on first character
6270                         prefix = prefix.toLowerCase();
6271                     }
6272                     tmpProp = prefix + property.charAt(0).toUpperCase() + property.slice(1);
6273                 } else {
6274                     tmpProp = property;
6275                 }
6276
6277                 if(obj[tmpProp] !== undefined) {
6278                     jsCache[property] = tmpProp;
6279                     break;
6280                 }
6281             }
6282         }
6283         return jsCache[property];
6284     }
6285     
6286     /**
6287      * APIMethod: style
6288      * Detect which property is used for a DOM style property
6289      *
6290      * Parameters:
6291      * property - {String} The standard (unprefixed) style property name
6292      *
6293      * Returns:
6294      * {String} The standard style property, prefixed property or null if not
6295      *          supported
6296      */
6297     function style(property) {
6298         return js(divStyle, property);
6299     }
6300     
6301     return {
6302         css:      css,
6303         js:       js,
6304         style:    style,
6305         
6306         // used for testing
6307         cssCache:       cssCache,
6308         jsCache:        jsCache
6309     };
6310 }());
6311 /* ======================================================================
6312     OpenLayers/Animation.js
6313    ====================================================================== */
6314
6315 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
6316  * full list of contributors). Published under the 2-clause BSD license.
6317  * See license.txt in the OpenLayers distribution or repository for the
6318  * full text of the license. */
6319
6320 /**
6321  * @requires OpenLayers/SingleFile.js
6322  * @requires OpenLayers/Util/vendorPrefix.js
6323  */
6324
6325 /**
6326  * Namespace: OpenLayers.Animation
6327  * A collection of utility functions for executing methods that repaint a 
6328  *     portion of the browser window.  These methods take advantage of the
6329  *     browser's scheduled repaints where requestAnimationFrame is available.
6330  */
6331 OpenLayers.Animation = (function(window) {
6332     
6333     /**
6334      * Property: isNative
6335      * {Boolean} true if a native requestAnimationFrame function is available
6336      */
6337     var requestAnimationFrame = OpenLayers.Util.vendorPrefix.js(window, "requestAnimationFrame");
6338     var isNative = !!(requestAnimationFrame);
6339     
6340     /**
6341      * Function: requestFrame
6342      * Schedule a function to be called at the next available animation frame.
6343      *     Uses the native method where available.  Where requestAnimationFrame is
6344      *     not available, setTimeout will be called with a 16ms delay.
6345      *
6346      * Parameters:
6347      * callback - {Function} The function to be called at the next animation frame.
6348      * element - {DOMElement} Optional element that visually bounds the animation.
6349      */
6350     var requestFrame = (function() {
6351         var request = window[requestAnimationFrame] ||
6352             function(callback, element) {
6353                 window.setTimeout(callback, 16);
6354             };
6355         // bind to window to avoid illegal invocation of native function
6356         return function(callback, element) {
6357             request.apply(window, [callback, element]);
6358         };
6359     })();
6360     
6361     // private variables for animation loops
6362     var counter = 0;
6363     var loops = {};
6364     
6365     /**
6366      * Function: start
6367      * Executes a method with <requestFrame> in series for some 
6368      *     duration.
6369      *
6370      * Parameters:
6371      * callback - {Function} The function to be called at the next animation frame.
6372      * duration - {Number} Optional duration for the loop.  If not provided, the
6373      *     animation loop will execute indefinitely.
6374      * element - {DOMElement} Optional element that visually bounds the animation.
6375      *
6376      * Returns:
6377      * {Number} Identifier for the animation loop.  Used to stop animations with
6378      *     <stop>.
6379      */
6380     function start(callback, duration, element) {
6381         duration = duration > 0 ? duration : Number.POSITIVE_INFINITY;
6382         var id = ++counter;
6383         var start = +new Date;
6384         loops[id] = function() {
6385             if (loops[id] && +new Date - start <= duration) {
6386                 callback();
6387                 if (loops[id]) {
6388                     requestFrame(loops[id], element);
6389                 }
6390             } else {
6391                 delete loops[id];
6392             }
6393         };
6394         requestFrame(loops[id], element);
6395         return id;
6396     }
6397     
6398     /**
6399      * Function: stop
6400      * Terminates an animation loop started with <start>.
6401      *
6402      * Parameters:
6403      * id - {Number} Identifier returned from <start>.
6404      */
6405     function stop(id) {
6406         delete loops[id];
6407     }
6408     
6409     return {
6410         isNative: isNative,
6411         requestFrame: requestFrame,
6412         start: start,
6413         stop: stop
6414     };
6415     
6416 })(window);
6417 /* ======================================================================
6418     OpenLayers/Tween.js
6419    ====================================================================== */
6420
6421 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
6422  * full list of contributors). Published under the 2-clause BSD license.
6423  * See license.txt in the OpenLayers distribution or repository for the
6424  * full text of the license. */
6425
6426 /**
6427  * @requires OpenLayers/BaseTypes/Class.js
6428  * @requires OpenLayers/Animation.js
6429  */
6430
6431 /**
6432  * Namespace: OpenLayers.Tween
6433  */
6434 OpenLayers.Tween = OpenLayers.Class({
6435     
6436     /**
6437      * APIProperty: easing
6438      * {<OpenLayers.Easing>(Function)} Easing equation used for the animation
6439      *     Defaultly set to OpenLayers.Easing.Expo.easeOut
6440      */
6441     easing: null,
6442     
6443     /**
6444      * APIProperty: begin
6445      * {Object} Values to start the animation with
6446      */
6447     begin: null,
6448     
6449     /**
6450      * APIProperty: finish
6451      * {Object} Values to finish the animation with
6452      */
6453     finish: null,
6454     
6455     /**
6456      * APIProperty: duration
6457      * {int} duration of the tween (number of steps)
6458      */
6459     duration: null,
6460     
6461     /**
6462      * APIProperty: callbacks
6463      * {Object} An object with start, eachStep and done properties whose values
6464      *     are functions to be call during the animation. They are passed the
6465      *     current computed value as argument.
6466      */
6467     callbacks: null,
6468     
6469     /**
6470      * Property: time
6471      * {int} Step counter
6472      */
6473     time: null,
6474     
6475     /**
6476      * APIProperty: minFrameRate
6477      * {Number} The minimum framerate for animations in frames per second. After
6478      * each step, the time spent in the animation is compared to the calculated
6479      * time at this frame rate. If the animation runs longer than the calculated
6480      * time, the next step is skipped. Default is 30.
6481      */
6482     minFrameRate: null,
6483
6484     /**
6485      * Property: startTime
6486      * {Number} The timestamp of the first execution step. Used for skipping
6487      * frames
6488      */
6489     startTime: null,
6490     
6491     /**
6492      * Property: animationId
6493      * {int} Loop id returned by OpenLayers.Animation.start
6494      */
6495     animationId: null,
6496     
6497     /**
6498      * Property: playing
6499      * {Boolean} Tells if the easing is currently playing
6500      */
6501     playing: false,
6502     
6503     /** 
6504      * Constructor: OpenLayers.Tween
6505      * Creates a Tween.
6506      *
6507      * Parameters:
6508      * easing - {<OpenLayers.Easing>(Function)} easing function method to use
6509      */ 
6510     initialize: function(easing) {
6511         this.easing = (easing) ? easing : OpenLayers.Easing.Expo.easeOut;
6512     },
6513     
6514     /**
6515      * APIMethod: start
6516      * Plays the Tween, and calls the callback method on each step
6517      * 
6518      * Parameters:
6519      * begin - {Object} values to start the animation with
6520      * finish - {Object} values to finish the animation with
6521      * duration - {int} duration of the tween (number of steps)
6522      * options - {Object} hash of options (callbacks (start, eachStep, done),
6523      *     minFrameRate)
6524      */
6525     start: function(begin, finish, duration, options) {
6526         this.playing = true;
6527         this.begin = begin;
6528         this.finish = finish;
6529         this.duration = duration;
6530         this.callbacks = options.callbacks;
6531         this.minFrameRate = options.minFrameRate || 30;
6532         this.time = 0;
6533         this.startTime = new Date().getTime();
6534         OpenLayers.Animation.stop(this.animationId);
6535         this.animationId = null;
6536         if (this.callbacks && this.callbacks.start) {
6537             this.callbacks.start.call(this, this.begin);
6538         }
6539         this.animationId = OpenLayers.Animation.start(
6540             OpenLayers.Function.bind(this.play, this)
6541         );
6542     },
6543     
6544     /**
6545      * APIMethod: stop
6546      * Stops the Tween, and calls the done callback
6547      *     Doesn't do anything if animation is already finished
6548      */
6549     stop: function() {
6550         if (!this.playing) {
6551             return;
6552         }
6553         
6554         if (this.callbacks && this.callbacks.done) {
6555             this.callbacks.done.call(this, this.finish);
6556         }
6557         OpenLayers.Animation.stop(this.animationId);
6558         this.animationId = null;
6559         this.playing = false;
6560     },
6561     
6562     /**
6563      * Method: play
6564      * Calls the appropriate easing method
6565      */
6566     play: function() {
6567         var value = {};
6568         for (var i in this.begin) {
6569             var b = this.begin[i];
6570             var f = this.finish[i];
6571             if (b == null || f == null || isNaN(b) || isNaN(f)) {
6572                 throw new TypeError('invalid value for Tween');
6573             }
6574
6575             var c = f - b;
6576             value[i] = this.easing.apply(this, [this.time, b, c, this.duration]);
6577         }
6578         this.time++;
6579         
6580         if (this.callbacks && this.callbacks.eachStep) {
6581             // skip frames if frame rate drops below threshold
6582             if ((new Date().getTime() - this.startTime) / this.time <= 1000 / this.minFrameRate) {
6583                 this.callbacks.eachStep.call(this, value);
6584             }
6585         }
6586         
6587         if (this.time > this.duration) {
6588             this.stop();
6589         }
6590     },
6591     
6592     /**
6593      * Create empty functions for all easing methods.
6594      */
6595     CLASS_NAME: "OpenLayers.Tween"
6596 });
6597
6598 /**
6599  * Namespace: OpenLayers.Easing
6600  * 
6601  * Credits:
6602  *      Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>
6603  */
6604 OpenLayers.Easing = {
6605     /**
6606      * Create empty functions for all easing methods.
6607      */
6608     CLASS_NAME: "OpenLayers.Easing"
6609 };
6610
6611 /**
6612  * Namespace: OpenLayers.Easing.Linear
6613  */
6614 OpenLayers.Easing.Linear = {
6615     
6616     /**
6617      * Function: easeIn
6618      * 
6619      * Parameters:
6620      * t - {Float} time
6621      * b - {Float} beginning position
6622      * c - {Float} total change
6623      * d - {Float} duration of the transition
6624      *
6625      * Returns:
6626      * {Float}
6627      */
6628     easeIn: function(t, b, c, d) {
6629         return c*t/d + b;
6630     },
6631     
6632     /**
6633      * Function: easeOut
6634      * 
6635      * Parameters:
6636      * t - {Float} time
6637      * b - {Float} beginning position
6638      * c - {Float} total change
6639      * d - {Float} duration of the transition
6640      *
6641      * Returns:
6642      * {Float}
6643      */
6644     easeOut: function(t, b, c, d) {
6645         return c*t/d + b;
6646     },
6647     
6648     /**
6649      * Function: easeInOut
6650      * 
6651      * Parameters:
6652      * t - {Float} time
6653      * b - {Float} beginning position
6654      * c - {Float} total change
6655      * d - {Float} duration of the transition
6656      *
6657      * Returns:
6658      * {Float}
6659      */
6660     easeInOut: function(t, b, c, d) {
6661         return c*t/d + b;
6662     },
6663
6664     CLASS_NAME: "OpenLayers.Easing.Linear"
6665 };
6666
6667 /**
6668  * Namespace: OpenLayers.Easing.Expo
6669  */
6670 OpenLayers.Easing.Expo = {
6671     
6672     /**
6673      * Function: easeIn
6674      * 
6675      * Parameters:
6676      * t - {Float} time
6677      * b - {Float} beginning position
6678      * c - {Float} total change
6679      * d - {Float} duration of the transition
6680      *
6681      * Returns:
6682      * {Float}
6683      */
6684     easeIn: function(t, b, c, d) {
6685         return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
6686     },
6687     
6688     /**
6689      * Function: easeOut
6690      * 
6691      * Parameters:
6692      * t - {Float} time
6693      * b - {Float} beginning position
6694      * c - {Float} total change
6695      * d - {Float} duration of the transition
6696      *
6697      * Returns:
6698      * {Float}
6699      */
6700     easeOut: function(t, b, c, d) {
6701         return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
6702     },
6703     
6704     /**
6705      * Function: easeInOut
6706      * 
6707      * Parameters:
6708      * t - {Float} time
6709      * b - {Float} beginning position
6710      * c - {Float} total change
6711      * d - {Float} duration of the transition
6712      *
6713      * Returns:
6714      * {Float}
6715      */
6716     easeInOut: function(t, b, c, d) {
6717         if (t==0) return b;
6718         if (t==d) return b+c;
6719         if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
6720         return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
6721     },
6722
6723     CLASS_NAME: "OpenLayers.Easing.Expo"
6724 };
6725
6726 /**
6727  * Namespace: OpenLayers.Easing.Quad
6728  */
6729 OpenLayers.Easing.Quad = {
6730     
6731     /**
6732      * Function: easeIn
6733      * 
6734      * Parameters:
6735      * t - {Float} time
6736      * b - {Float} beginning position
6737      * c - {Float} total change
6738      * d - {Float} duration of the transition
6739      *
6740      * Returns:
6741      * {Float}
6742      */
6743     easeIn: function(t, b, c, d) {
6744         return c*(t/=d)*t + b;
6745     },
6746     
6747     /**
6748      * Function: easeOut
6749      * 
6750      * Parameters:
6751      * t - {Float} time
6752      * b - {Float} beginning position
6753      * c - {Float} total change
6754      * d - {Float} duration of the transition
6755      *
6756      * Returns:
6757      * {Float}
6758      */
6759     easeOut: function(t, b, c, d) {
6760         return -c *(t/=d)*(t-2) + b;
6761     },
6762     
6763     /**
6764      * Function: easeInOut
6765      * 
6766      * Parameters:
6767      * t - {Float} time
6768      * b - {Float} beginning position
6769      * c - {Float} total change
6770      * d - {Float} duration of the transition
6771      *
6772      * Returns:
6773      * {Float}
6774      */
6775     easeInOut: function(t, b, c, d) {
6776         if ((t/=d/2) < 1) return c/2*t*t + b;
6777         return -c/2 * ((--t)*(t-2) - 1) + b;
6778     },
6779
6780     CLASS_NAME: "OpenLayers.Easing.Quad"
6781 };
6782 /* ======================================================================
6783     OpenLayers/Projection.js
6784    ====================================================================== */
6785
6786 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
6787  * full list of contributors). Published under the 2-clause BSD license.
6788  * See license.txt in the OpenLayers distribution or repository for the
6789  * full text of the license. */
6790
6791 /**
6792  * @requires OpenLayers/BaseTypes/Class.js
6793  * @requires OpenLayers/Util.js
6794  */
6795
6796 /**
6797  * Namespace: OpenLayers.Projection
6798  * Methods for coordinate transforms between coordinate systems.  By default,
6799  *     OpenLayers ships with the ability to transform coordinates between
6800  *     geographic (EPSG:4326) and web or spherical mercator (EPSG:900913 et al.)
6801  *     coordinate reference systems.  See the <transform> method for details
6802  *     on usage.
6803  *
6804  * Additional transforms may be added by using the <proj4js at http://proj4js.org/>
6805  *     library.  If the proj4js library is included, the <transform> method 
6806  *     will work between any two coordinate reference systems with proj4js 
6807  *     definitions.
6808  *
6809  * If the proj4js library is not included, or if you wish to allow transforms
6810  *     between arbitrary coordinate reference systems, use the <addTransform>
6811  *     method to register a custom transform method.
6812  */
6813 OpenLayers.Projection = OpenLayers.Class({
6814
6815     /**
6816      * Property: proj
6817      * {Object} Proj4js.Proj instance.
6818      */
6819     proj: null,
6820     
6821     /**
6822      * Property: projCode
6823      * {String}
6824      */
6825     projCode: null,
6826     
6827     /**
6828      * Property: titleRegEx
6829      * {RegExp} regular expression to strip the title from a proj4js definition
6830      */
6831     titleRegEx: /\+title=[^\+]*/,
6832
6833     /**
6834      * Constructor: OpenLayers.Projection
6835      * This class offers several methods for interacting with a wrapped 
6836      *     pro4js projection object. 
6837      *
6838      * Parameters:
6839      * projCode - {String} A string identifying the Well Known Identifier for
6840      *    the projection.
6841      * options - {Object} An optional object to set additional properties
6842      *     on the projection.
6843      *
6844      * Returns:
6845      * {<OpenLayers.Projection>} A projection object.
6846      */
6847     initialize: function(projCode, options) {
6848         OpenLayers.Util.extend(this, options);
6849         this.projCode = projCode;
6850         if (typeof Proj4js == "object") {
6851             this.proj = new Proj4js.Proj(projCode);
6852         }
6853     },
6854     
6855     /**
6856      * APIMethod: getCode
6857      * Get the string SRS code.
6858      *
6859      * Returns:
6860      * {String} The SRS code.
6861      */
6862     getCode: function() {
6863         return this.proj ? this.proj.srsCode : this.projCode;
6864     },
6865    
6866     /**
6867      * APIMethod: getUnits
6868      * Get the units string for the projection -- returns null if 
6869      *     proj4js is not available.
6870      *
6871      * Returns:
6872      * {String} The units abbreviation.
6873      */
6874     getUnits: function() {
6875         return this.proj ? this.proj.units : null;
6876     },
6877
6878     /**
6879      * Method: toString
6880      * Convert projection to string (getCode wrapper).
6881      *
6882      * Returns:
6883      * {String} The projection code.
6884      */
6885     toString: function() {
6886         return this.getCode();
6887     },
6888
6889     /**
6890      * Method: equals
6891      * Test equality of two projection instances.  Determines equality based
6892      *     solely on the projection code.
6893      *
6894      * Returns:
6895      * {Boolean} The two projections are equivalent.
6896      */
6897     equals: function(projection) {
6898         var p = projection, equals = false;
6899         if (p) {
6900             if (!(p instanceof OpenLayers.Projection)) {
6901                 p = new OpenLayers.Projection(p);
6902             }
6903             if ((typeof Proj4js == "object") && this.proj.defData && p.proj.defData) {
6904                 equals = this.proj.defData.replace(this.titleRegEx, "") ==
6905                     p.proj.defData.replace(this.titleRegEx, "");
6906             } else if (p.getCode) {
6907                 var source = this.getCode(), target = p.getCode();
6908                 equals = source == target ||
6909                     !!OpenLayers.Projection.transforms[source] &&
6910                     OpenLayers.Projection.transforms[source][target] ===
6911                         OpenLayers.Projection.nullTransform;
6912             }
6913         }
6914         return equals;   
6915     },
6916
6917     /* Method: destroy
6918      * Destroy projection object.
6919      */
6920     destroy: function() {
6921         delete this.proj;
6922         delete this.projCode;
6923     },
6924     
6925     CLASS_NAME: "OpenLayers.Projection" 
6926 });     
6927
6928 /**
6929  * Property: transforms
6930  * {Object} Transforms is an object, with from properties, each of which may
6931  * have a to property. This allows you to define projections without 
6932  * requiring support for proj4js to be included.
6933  *
6934  * This object has keys which correspond to a 'source' projection object.  The
6935  * keys should be strings, corresponding to the projection.getCode() value.
6936  * Each source projection object should have a set of destination projection
6937  * keys included in the object. 
6938  * 
6939  * Each value in the destination object should be a transformation function,
6940  * where the function is expected to be passed an object with a .x and a .y
6941  * property.  The function should return the object, with the .x and .y
6942  * transformed according to the transformation function.
6943  *
6944  * Note - Properties on this object should not be set directly.  To add a
6945  *     transform method to this object, use the <addTransform> method.  For an
6946  *     example of usage, see the OpenLayers.Layer.SphericalMercator file.
6947  */
6948 OpenLayers.Projection.transforms = {};
6949
6950 /**
6951  * APIProperty: defaults
6952  * {Object} Defaults for the SRS codes known to OpenLayers (currently
6953  * EPSG:4326, CRS:84, urn:ogc:def:crs:EPSG:6.6:4326, EPSG:900913, EPSG:3857,
6954  * EPSG:102113, EPSG:102100 and OSGEO:41001). Keys are the SRS code, values are
6955  * units, maxExtent (the validity extent for the SRS in projected coordinates),
6956  * worldExtent (the world's extent in EPSG:4326) and yx (true if this SRS
6957  * is known to have a reverse axis order).
6958  */
6959 OpenLayers.Projection.defaults = {
6960     "EPSG:4326": {
6961         units: "degrees",
6962         maxExtent: [-180, -90, 180, 90],
6963         worldExtent: [-180, -90, 180, 90],
6964         yx: true
6965     },
6966     "CRS:84": {
6967         units: "degrees",
6968         maxExtent: [-180, -90, 180, 90],
6969         worldExtent: [-180, -90, 180, 90]
6970     },
6971     "EPSG:900913": {
6972         units: "m",
6973         maxExtent: [-20037508.34, -20037508.34, 20037508.34, 20037508.34],
6974         worldExtent: [-180, -89, 180, 89]
6975     }
6976 };
6977
6978 /**
6979  * APIMethod: addTransform
6980  * Set a custom transform method between two projections.  Use this method in
6981  *     cases where the proj4js lib is not available or where custom projections
6982  *     need to be handled.
6983  *
6984  * Parameters:
6985  * from - {String} The code for the source projection
6986  * to - {String} the code for the destination projection
6987  * method - {Function} A function that takes a point as an argument and
6988  *     transforms that point from the source to the destination projection
6989  *     in place.  The original point should be modified.
6990  */
6991 OpenLayers.Projection.addTransform = function(from, to, method) {
6992     if (method === OpenLayers.Projection.nullTransform) {
6993         var defaults = OpenLayers.Projection.defaults[from];
6994         if (defaults && !OpenLayers.Projection.defaults[to]) {
6995             OpenLayers.Projection.defaults[to] = defaults;
6996         }
6997     }
6998     if(!OpenLayers.Projection.transforms[from]) {
6999         OpenLayers.Projection.transforms[from] = {};
7000     }
7001     OpenLayers.Projection.transforms[from][to] = method;
7002 };
7003
7004 /**
7005  * APIMethod: transform
7006  * Transform a point coordinate from one projection to another.  Note that
7007  *     the input point is transformed in place.
7008  * 
7009  * Parameters:
7010  * point - {<OpenLayers.Geometry.Point> | Object} An object with x and y
7011  *     properties representing coordinates in those dimensions.
7012  * source - {OpenLayers.Projection} Source map coordinate system
7013  * dest - {OpenLayers.Projection} Destination map coordinate system
7014  *
7015  * Returns:
7016  * point - {object} A transformed coordinate.  The original point is modified.
7017  */
7018 OpenLayers.Projection.transform = function(point, source, dest) {
7019     if (source && dest) {
7020         if (!(source instanceof OpenLayers.Projection)) {
7021             source = new OpenLayers.Projection(source);
7022         }
7023         if (!(dest instanceof OpenLayers.Projection)) {
7024             dest = new OpenLayers.Projection(dest);
7025         }
7026         if (source.proj && dest.proj) {
7027             point = Proj4js.transform(source.proj, dest.proj, point);
7028         } else {
7029             var sourceCode = source.getCode();
7030             var destCode = dest.getCode();
7031             var transforms = OpenLayers.Projection.transforms;
7032             if (transforms[sourceCode] && transforms[sourceCode][destCode]) {
7033                 transforms[sourceCode][destCode](point);
7034             }
7035         }
7036     }
7037     return point;
7038 };
7039
7040 /**
7041  * APIFunction: nullTransform
7042  * A null transformation - useful for defining projection aliases when
7043  * proj4js is not available:
7044  *
7045  * (code)
7046  * OpenLayers.Projection.addTransform("EPSG:3857", "EPSG:900913",
7047  *     OpenLayers.Projection.nullTransform);
7048  * OpenLayers.Projection.addTransform("EPSG:900913", "EPSG:3857",
7049  *     OpenLayers.Projection.nullTransform);
7050  * (end)
7051  */
7052 OpenLayers.Projection.nullTransform = function(point) {
7053     return point;
7054 };
7055
7056 /**
7057  * Note: Transforms for web mercator <-> geographic
7058  * OpenLayers recognizes EPSG:3857, EPSG:900913, EPSG:102113, EPSG:102100 and 
7059  * OSGEO:41001. OpenLayers originally started referring to EPSG:900913 as web
7060  * mercator. The EPSG has declared EPSG:3857 to be web mercator.
7061  * ArcGIS 10 recognizes the EPSG:3857, EPSG:102113, and EPSG:102100 as
7062  * equivalent.  See http://blogs.esri.com/Dev/blogs/arcgisserver/archive/2009/11/20/ArcGIS-Online-moving-to-Google-_2F00_-Bing-tiling-scheme_3A00_-What-does-this-mean-for-you_3F00_.aspx#12084.
7063  * For geographic, OpenLayers recognizes EPSG:4326, CRS:84 and
7064  * urn:ogc:def:crs:EPSG:6.6:4326. OpenLayers also knows about the reverse axis
7065  * order for EPSG:4326. 
7066  */
7067 (function() {
7068
7069     var pole = 20037508.34;
7070
7071     function inverseMercator(xy) {
7072         xy.x = 180 * xy.x / pole;
7073         xy.y = 180 / Math.PI * (2 * Math.atan(Math.exp((xy.y / pole) * Math.PI)) - Math.PI / 2);
7074         return xy;
7075     }
7076
7077     function forwardMercator(xy) {
7078         xy.x = xy.x * pole / 180;
7079         var y = Math.log(Math.tan((90 + xy.y) * Math.PI / 360)) / Math.PI * pole;
7080         xy.y = Math.max(-20037508.34, Math.min(y, 20037508.34));
7081         return xy;
7082     }
7083
7084     function map(base, codes) {
7085         var add = OpenLayers.Projection.addTransform;
7086         var same = OpenLayers.Projection.nullTransform;
7087         var i, len, code, other, j;
7088         for (i=0, len=codes.length; i<len; ++i) {
7089             code = codes[i];
7090             add(base, code, forwardMercator);
7091             add(code, base, inverseMercator);
7092             for (j=i+1; j<len; ++j) {
7093                 other = codes[j];
7094                 add(code, other, same);
7095                 add(other, code, same);
7096             }
7097         }
7098     }
7099     
7100     // list of equivalent codes for web mercator
7101     var mercator = ["EPSG:900913", "EPSG:3857", "EPSG:102113", "EPSG:102100", "OSGEO:41001"],
7102         geographic = ["CRS:84", "urn:ogc:def:crs:EPSG:6.6:4326", "EPSG:4326"],
7103         i;
7104     for (i=mercator.length-1; i>=0; --i) {
7105         map(mercator[i], geographic);
7106     }
7107     for (i=geographic.length-1; i>=0; --i) {
7108         map(geographic[i], mercator);
7109     }
7110
7111 })();
7112 /* ======================================================================
7113     OpenLayers/Map.js
7114    ====================================================================== */
7115
7116 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
7117  * full list of contributors). Published under the 2-clause BSD license.
7118  * See license.txt in the OpenLayers distribution or repository for the
7119  * full text of the license. */
7120
7121 /**
7122  * @requires OpenLayers/BaseTypes/Class.js
7123  * @requires OpenLayers/Util.js
7124  * @requires OpenLayers/Util/vendorPrefix.js
7125  * @requires OpenLayers/Events.js
7126  * @requires OpenLayers/Tween.js
7127  * @requires OpenLayers/Projection.js
7128  */
7129
7130 /**
7131  * Class: OpenLayers.Map
7132  * Instances of OpenLayers.Map are interactive maps embedded in a web page.
7133  * Create a new map with the <OpenLayers.Map> constructor.
7134  * 
7135  * On their own maps do not provide much functionality.  To extend a map
7136  * it's necessary to add controls (<OpenLayers.Control>) and 
7137  * layers (<OpenLayers.Layer>) to the map. 
7138  */
7139 OpenLayers.Map = OpenLayers.Class({
7140     
7141     /**
7142      * Constant: Z_INDEX_BASE
7143      * {Object} Base z-indexes for different classes of thing 
7144      */
7145     Z_INDEX_BASE: {
7146         BaseLayer: 100,
7147         Overlay: 325,
7148         Feature: 725,
7149         Popup: 750,
7150         Control: 1000
7151     },
7152
7153     /**
7154      * APIProperty: events
7155      * {<OpenLayers.Events>}
7156      *
7157      * Register a listener for a particular event with the following syntax:
7158      * (code)
7159      * map.events.register(type, obj, listener);
7160      * (end)
7161      *
7162      * Listeners will be called with a reference to an event object.  The
7163      *     properties of this event depends on exactly what happened.
7164      *
7165      * All event objects have at least the following properties:
7166      * object - {Object} A reference to map.events.object.
7167      * element - {DOMElement} A reference to map.events.element.
7168      *
7169      * Browser events have the following additional properties:
7170      * xy - {<OpenLayers.Pixel>} The pixel location of the event (relative
7171      *     to the the map viewport).
7172      *
7173      * Supported map event types:
7174      * preaddlayer - triggered before a layer has been added.  The event
7175      *     object will include a *layer* property that references the layer  
7176      *     to be added. When a listener returns "false" the adding will be 
7177      *     aborted.
7178      * addlayer - triggered after a layer has been added.  The event object
7179      *     will include a *layer* property that references the added layer.
7180      * preremovelayer - triggered before a layer has been removed. The event
7181      *     object will include a *layer* property that references the layer  
7182      *     to be removed. When a listener returns "false" the removal will be 
7183      *     aborted.
7184      * removelayer - triggered after a layer has been removed.  The event
7185      *     object will include a *layer* property that references the removed
7186      *     layer.
7187      * changelayer - triggered after a layer name change, order change,
7188      *     opacity change, params change, visibility change (actual visibility,
7189      *     not the layer's visibility property) or attribution change (due to
7190      *     extent change). Listeners will receive an event object with *layer*
7191      *     and *property* properties. The *layer* property will be a reference
7192      *     to the changed layer. The *property* property will be a key to the
7193      *     changed property (name, order, opacity, params, visibility or
7194      *     attribution).
7195      * movestart - triggered after the start of a drag, pan, or zoom. The event
7196      *     object may include a *zoomChanged* property that tells whether the
7197      *     zoom has changed.
7198      * move - triggered after each drag, pan, or zoom
7199      * moveend - triggered after a drag, pan, or zoom completes
7200      * zoomstart - triggered when a zoom starts. Listeners receive an object
7201      *     with *center* and *zoom* properties, for the target center and zoom
7202      *     level.
7203      * zoomend - triggered after a zoom completes
7204      * mouseover - triggered after mouseover the map
7205      * mouseout - triggered after mouseout the map
7206      * mousemove - triggered after mousemove the map
7207      * changebaselayer - triggered after the base layer changes
7208      * updatesize - triggered after the <updateSize> method was executed
7209      */
7210
7211     /**
7212      * Property: id
7213      * {String} Unique identifier for the map
7214      */
7215     id: null,
7216     
7217     /**
7218      * Property: fractionalZoom
7219      * {Boolean} For a base layer that supports it, allow the map resolution
7220      *     to be set to a value between one of the values in the resolutions
7221      *     array.  Default is false.
7222      *
7223      * When fractionalZoom is set to true, it is possible to zoom to
7224      *     an arbitrary extent.  This requires a base layer from a source
7225      *     that supports requests for arbitrary extents (i.e. not cached
7226      *     tiles on a regular lattice).  This means that fractionalZoom
7227      *     will not work with commercial layers (Google, Yahoo, VE), layers
7228      *     using TileCache, or any other pre-cached data sources.
7229      *
7230      * If you are using fractionalZoom, then you should also use
7231      *     <getResolutionForZoom> instead of layer.resolutions[zoom] as the
7232      *     former works for non-integer zoom levels.
7233      */
7234     fractionalZoom: false,
7235     
7236     /**
7237      * APIProperty: events
7238      * {<OpenLayers.Events>} An events object that handles all 
7239      *                       events on the map
7240      */
7241     events: null,
7242     
7243     /**
7244      * APIProperty: allOverlays
7245      * {Boolean} Allow the map to function with "overlays" only.  Defaults to
7246      *     false.  If true, the lowest layer in the draw order will act as
7247      *     the base layer.  In addition, if set to true, all layers will
7248      *     have isBaseLayer set to false when they are added to the map.
7249      *
7250      * Note:
7251      * If you set map.allOverlays to true, then you *cannot* use
7252      *     map.setBaseLayer or layer.setIsBaseLayer.  With allOverlays true,
7253      *     the lowest layer in the draw layer is the base layer.  So, to change
7254      *     the base layer, use <setLayerIndex> or <raiseLayer> to set the layer
7255      *     index to 0.
7256      */
7257     allOverlays: false,
7258
7259     /**
7260      * APIProperty: div
7261      * {DOMElement|String} The element that contains the map (or an id for
7262      *     that element).  If the <OpenLayers.Map> constructor is called
7263      *     with two arguments, this should be provided as the first argument.
7264      *     Alternatively, the map constructor can be called with the options
7265      *     object as the only argument.  In this case (one argument), a
7266      *     div property may or may not be provided.  If the div property
7267      *     is not provided, the map can be rendered to a container later
7268      *     using the <render> method.
7269      *     
7270      * Note:
7271      * If you are calling <render> after map construction, do not use
7272      *     <maxResolution>  auto.  Instead, divide your <maxExtent> by your
7273      *     maximum expected dimension.
7274      */
7275     div: null,
7276     
7277     /**
7278      * Property: dragging
7279      * {Boolean} The map is currently being dragged.
7280      */
7281     dragging: false,
7282
7283     /**
7284      * Property: size
7285      * {<OpenLayers.Size>} Size of the main div (this.div)
7286      */
7287     size: null,
7288     
7289     /**
7290      * Property: viewPortDiv
7291      * {HTMLDivElement} The element that represents the map viewport
7292      */
7293     viewPortDiv: null,
7294
7295     /**
7296      * Property: layerContainerOrigin
7297      * {<OpenLayers.LonLat>} The lonlat at which the later container was
7298      *                       re-initialized (on-zoom)
7299      */
7300     layerContainerOrigin: null,
7301
7302     /**
7303      * Property: layerContainerDiv
7304      * {HTMLDivElement} The element that contains the layers.
7305      */
7306     layerContainerDiv: null,
7307
7308     /**
7309      * APIProperty: layers
7310      * {Array(<OpenLayers.Layer>)} Ordered list of layers in the map
7311      */
7312     layers: null,
7313
7314     /**
7315      * APIProperty: controls
7316      * {Array(<OpenLayers.Control>)} List of controls associated with the map.
7317      *
7318      * If not provided in the map options at construction, the map will
7319      *     by default be given the following controls if present in the build:
7320      *  - <OpenLayers.Control.Navigation> or <OpenLayers.Control.TouchNavigation>
7321      *  - <OpenLayers.Control.Zoom> or <OpenLayers.Control.PanZoom>
7322      *  - <OpenLayers.Control.ArgParser>
7323      *  - <OpenLayers.Control.Attribution>
7324      */
7325     controls: null,
7326
7327     /**
7328      * Property: popups
7329      * {Array(<OpenLayers.Popup>)} List of popups associated with the map
7330      */
7331     popups: null,
7332
7333     /**
7334      * APIProperty: baseLayer
7335      * {<OpenLayers.Layer>} The currently selected base layer.  This determines
7336      * min/max zoom level, projection, etc.
7337      */
7338     baseLayer: null,
7339     
7340     /**
7341      * Property: center
7342      * {<OpenLayers.LonLat>} The current center of the map
7343      */
7344     center: null,
7345
7346     /**
7347      * Property: resolution
7348      * {Float} The resolution of the map.
7349      */
7350     resolution: null,
7351
7352     /**
7353      * Property: zoom
7354      * {Integer} The current zoom level of the map
7355      */
7356     zoom: 0,    
7357
7358     /**
7359      * Property: panRatio
7360      * {Float} The ratio of the current extent within
7361      *         which panning will tween.
7362      */
7363     panRatio: 1.5,    
7364
7365     /**
7366      * APIProperty: options
7367      * {Object} The options object passed to the class constructor. Read-only.
7368      */
7369     options: null,
7370
7371   // Options
7372
7373     /**
7374      * APIProperty: tileSize
7375      * {<OpenLayers.Size>} Set in the map options to override the default tile
7376      *                     size for this map.
7377      */
7378     tileSize: null,
7379
7380     /**
7381      * APIProperty: projection
7382      * {String} Set in the map options to specify the default projection 
7383      *          for layers added to this map. When using a projection other than EPSG:4326
7384      *          (CRS:84, Geographic) or EPSG:3857 (EPSG:900913, Web Mercator),
7385      *          also set maxExtent, maxResolution or resolutions.  Default is "EPSG:4326".
7386      *          Note that the projection of the map is usually determined
7387      *          by that of the current baseLayer (see <baseLayer> and <getProjectionObject>).
7388      */
7389     projection: "EPSG:4326",    
7390         
7391     /**
7392      * APIProperty: units
7393      * {String} The map units.  Possible values are 'degrees' (or 'dd'), 'm', 
7394      *     'ft', 'km', 'mi', 'inches'.  Normally taken from the projection.
7395      *     Only required if both map and layers do not define a projection,
7396      *     or if they define a projection which does not define units
7397      */
7398     units: null,
7399
7400     /**
7401      * APIProperty: resolutions
7402      * {Array(Float)} A list of map resolutions (map units per pixel) in 
7403      *     descending order.  If this is not set in the layer constructor, it 
7404      *     will be set based on other resolution related properties 
7405      *     (maxExtent, maxResolution, maxScale, etc.).
7406      */
7407     resolutions: null,
7408
7409     /**
7410      * APIProperty: maxResolution
7411      * {Float} Required if you are not displaying the whole world on a tile
7412      * with the size specified in <tileSize>.
7413      */
7414     maxResolution: null,
7415
7416     /**
7417      * APIProperty: minResolution
7418      * {Float}
7419      */
7420     minResolution: null,
7421
7422     /**
7423      * APIProperty: maxScale
7424      * {Float}
7425      */
7426     maxScale: null,
7427
7428     /**
7429      * APIProperty: minScale
7430      * {Float}
7431      */
7432     minScale: null,
7433
7434     /**
7435      * APIProperty: maxExtent
7436      * {<OpenLayers.Bounds>|Array} If provided as an array, the array
7437      *     should consist of four values (left, bottom, right, top).
7438      *     The maximum extent for the map.
7439      *     Default depends on projection; if this is one of those defined in OpenLayers.Projection.defaults
7440      *     (EPSG:4326 or web mercator), maxExtent will be set to the value defined there;
7441      *     else, defaults to null.
7442      *     To restrict user panning and zooming of the map, use <restrictedExtent> instead.
7443      *     The value for <maxExtent> will change calculations for tile URLs.
7444      */
7445     maxExtent: null,
7446     
7447     /**
7448      * APIProperty: minExtent
7449      * {<OpenLayers.Bounds>|Array} If provided as an array, the array
7450      *     should consist of four values (left, bottom, right, top).
7451      *     The minimum extent for the map.  Defaults to null.
7452      */
7453     minExtent: null,
7454     
7455     /**
7456      * APIProperty: restrictedExtent
7457      * {<OpenLayers.Bounds>|Array} If provided as an array, the array
7458      *     should consist of four values (left, bottom, right, top).
7459      *     Limit map navigation to this extent where possible.
7460      *     If a non-null restrictedExtent is set, panning will be restricted
7461      *     to the given bounds.  In addition, zooming to a resolution that
7462      *     displays more than the restricted extent will center the map
7463      *     on the restricted extent.  If you wish to limit the zoom level
7464      *     or resolution, use maxResolution.
7465      */
7466     restrictedExtent: null,
7467
7468     /**
7469      * APIProperty: numZoomLevels
7470      * {Integer} Number of zoom levels for the map.  Defaults to 16.  Set a
7471      *           different value in the map options if needed.
7472      */
7473     numZoomLevels: 16,
7474
7475     /**
7476      * APIProperty: theme
7477      * {String} Relative path to a CSS file from which to load theme styles.
7478      *          Specify null in the map options (e.g. {theme: null}) if you 
7479      *          want to get cascading style declarations - by putting links to 
7480      *          stylesheets or style declarations directly in your page.
7481      */
7482     theme: null,
7483     
7484     /** 
7485      * APIProperty: displayProjection
7486      * {<OpenLayers.Projection>} Requires proj4js support for projections other
7487      *     than EPSG:4326 or EPSG:900913/EPSG:3857. Projection used by
7488      *     several controls to display data to user. If this property is set,
7489      *     it will be set on any control which has a null displayProjection
7490      *     property at the time the control is added to the map. 
7491      */
7492     displayProjection: null,
7493
7494     /**
7495      * APIProperty: tileManager
7496      * {<OpenLayers.TileManager>|Object} By default, and if the build contains
7497      * TileManager.js, the map will use the TileManager to queue image requests
7498      * and to cache tile image elements. To create a map without a TileManager
7499      * configure the map with tileManager: null. To create a TileManager with
7500      * non-default options, supply the options instead or alternatively supply
7501      * an instance of {<OpenLayers.TileManager>}.
7502      */
7503
7504     /**
7505      * APIProperty: fallThrough
7506      * {Boolean} Should OpenLayers allow events on the map to fall through to
7507      *           other elements on the page, or should it swallow them? (#457)
7508      *           Default is to swallow.
7509      */
7510     fallThrough: false,
7511
7512     /**
7513      * APIProperty: autoUpdateSize
7514      * {Boolean} Should OpenLayers automatically update the size of the map
7515      * when the resize event is fired. Default is true.
7516      */
7517     autoUpdateSize: true,
7518     
7519     /**
7520      * APIProperty: eventListeners
7521      * {Object} If set as an option at construction, the eventListeners
7522      *     object will be registered with <OpenLayers.Events.on>.  Object
7523      *     structure must be a listeners object as shown in the example for
7524      *     the events.on method.
7525      */
7526     eventListeners: null,
7527
7528     /**
7529      * Property: panTween
7530      * {<OpenLayers.Tween>} Animated panning tween object, see panTo()
7531      */
7532     panTween: null,
7533
7534     /**
7535      * APIProperty: panMethod
7536      * {Function} The Easing function to be used for tweening.  Default is
7537      * OpenLayers.Easing.Expo.easeOut. Setting this to 'null' turns off
7538      * animated panning.
7539      */
7540     panMethod: OpenLayers.Easing.Expo.easeOut,
7541     
7542     /**
7543      * Property: panDuration
7544      * {Integer} The number of steps to be passed to the
7545      * OpenLayers.Tween.start() method when the map is
7546      * panned.
7547      * Default is 50.
7548      */
7549     panDuration: 50,
7550     
7551     /**
7552      * Property: zoomTween
7553      * {<OpenLayers.Tween>} Animated zooming tween object, see zoomTo()
7554      */
7555     zoomTween: null,
7556
7557     /**
7558      * APIProperty: zoomMethod
7559      * {Function} The Easing function to be used for tweening.  Default is
7560      * OpenLayers.Easing.Quad.easeOut. Setting this to 'null' turns off
7561      * animated zooming.
7562      */
7563     zoomMethod: OpenLayers.Easing.Quad.easeOut,
7564     
7565     /**
7566      * Property: zoomDuration
7567      * {Integer} The number of steps to be passed to the
7568      * OpenLayers.Tween.start() method when the map is zoomed.
7569      * Default is 20.
7570      */
7571     zoomDuration: 20,
7572     
7573     /**
7574      * Property: paddingForPopups
7575      * {<OpenLayers.Bounds>} Outside margin of the popup. Used to prevent 
7576      *     the popup from getting too close to the map border.
7577      */
7578     paddingForPopups : null,
7579     
7580     /**
7581      * Property: layerContainerOriginPx
7582      * {Object} Cached object representing the layer container origin (in pixels).
7583      */
7584     layerContainerOriginPx: null,
7585     
7586     /**
7587      * Property: minPx
7588      * {Object} An object with a 'x' and 'y' values that is the lower
7589      *     left of maxExtent in viewport pixel space.
7590      *     Used to verify in moveByPx that the new location we're moving to
7591      *     is valid. It is also used in the getLonLatFromViewPortPx function
7592      *     of Layer.
7593      */
7594     minPx: null,
7595     
7596     /**
7597      * Property: maxPx
7598      * {Object} An object with a 'x' and 'y' values that is the top
7599      *     right of maxExtent in viewport pixel space.
7600      *     Used to verify in moveByPx that the new location we're moving to
7601      *     is valid.
7602      */
7603     maxPx: null,
7604     
7605     /**
7606      * Constructor: OpenLayers.Map
7607      * Constructor for a new OpenLayers.Map instance.  There are two possible
7608      *     ways to call the map constructor.  See the examples below.
7609      *
7610      * Parameters:
7611      * div - {DOMElement|String}  The element or id of an element in your page
7612      *     that will contain the map.  May be omitted if the <div> option is
7613      *     provided or if you intend to call the <render> method later.
7614      * options - {Object} Optional object with properties to tag onto the map.
7615      *
7616      * Valid options (in addition to the listed API properties):
7617      * center - {<OpenLayers.LonLat>|Array} The default initial center of the map.
7618      *     If provided as array, the first value is the x coordinate,
7619      *     and the 2nd value is the y coordinate.
7620      *     Only specify if <layers> is provided.
7621      *     Note that if an ArgParser/Permalink control is present,
7622      *     and the querystring contains coordinates, center will be set
7623      *     by that, and this option will be ignored.
7624      * zoom - {Number} The initial zoom level for the map. Only specify if
7625      *     <layers> is provided.
7626      *     Note that if an ArgParser/Permalink control is present,
7627      *     and the querystring contains a zoom level, zoom will be set
7628      *     by that, and this option will be ignored.
7629      * 
7630      * Examples:
7631      * (code)
7632      * // create a map with default options in an element with the id "map1"
7633      * var map = new OpenLayers.Map("map1");
7634      *
7635      * // create a map with non-default options in an element with id "map2"
7636      * var options = {
7637      *     projection: "EPSG:3857",
7638      *     maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000),
7639      *     center: new OpenLayers.LonLat(-12356463.476333, 5621521.4854095)
7640      * };
7641      * var map = new OpenLayers.Map("map2", options);
7642      *
7643      * // map with non-default options - same as above but with a single argument,
7644      * // a restricted extent, and using arrays for bounds and center
7645      * var map = new OpenLayers.Map({
7646      *     div: "map_id",
7647      *     projection: "EPSG:3857",
7648      *     maxExtent: [-18924313.432222, -15538711.094146, 18924313.432222, 15538711.094146],
7649      *     restrictedExtent: [-13358338.893333, -9608371.5085962, 13358338.893333, 9608371.5085962],
7650      *     center: [-12356463.476333, 5621521.4854095]
7651      * });
7652      *
7653      * // create a map without a reference to a container - call render later
7654      * var map = new OpenLayers.Map({
7655      *     projection: "EPSG:3857",
7656      *     maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000)
7657      * });
7658      * (end)
7659      */    
7660     initialize: function (div, options) {
7661         
7662         // If only one argument is provided, check if it is an object.
7663         var isDOMElement = OpenLayers.Util.isElement(div);
7664         if(arguments.length === 1 && typeof div === "object" && !isDOMElement) {
7665             options = div;
7666             div = options && options.div;
7667         }
7668
7669         // Simple-type defaults are set in class definition. 
7670         //  Now set complex-type defaults 
7671         this.tileSize = new OpenLayers.Size(OpenLayers.Map.TILE_WIDTH,
7672                                             OpenLayers.Map.TILE_HEIGHT);
7673         
7674         this.paddingForPopups = new OpenLayers.Bounds(15, 15, 15, 15);
7675
7676         this.theme = OpenLayers._getScriptLocation() + 
7677                              'theme/default/style.css'; 
7678
7679         // backup original options
7680         this.options = OpenLayers.Util.extend({}, options);
7681
7682         // now override default options 
7683         OpenLayers.Util.extend(this, options);
7684         
7685         var projCode = this.projection instanceof OpenLayers.Projection ?
7686             this.projection.projCode : this.projection;
7687         OpenLayers.Util.applyDefaults(this, OpenLayers.Projection.defaults[projCode]);
7688         
7689         // allow extents and center to be arrays
7690         if (this.maxExtent && !(this.maxExtent instanceof OpenLayers.Bounds)) {
7691             this.maxExtent = new OpenLayers.Bounds(this.maxExtent);
7692         }
7693         if (this.minExtent && !(this.minExtent instanceof OpenLayers.Bounds)) {
7694             this.minExtent = new OpenLayers.Bounds(this.minExtent);
7695         }
7696         if (this.restrictedExtent && !(this.restrictedExtent instanceof OpenLayers.Bounds)) {
7697             this.restrictedExtent = new OpenLayers.Bounds(this.restrictedExtent);
7698         }
7699         if (this.center && !(this.center instanceof OpenLayers.LonLat)) {
7700             this.center = new OpenLayers.LonLat(this.center);
7701         }
7702
7703         // initialize layers array
7704         this.layers = [];
7705
7706         this.id = OpenLayers.Util.createUniqueID("OpenLayers.Map_");
7707
7708         this.div = OpenLayers.Util.getElement(div);
7709         if(!this.div) {
7710             this.div = document.createElement("div");
7711             this.div.style.height = "1px";
7712             this.div.style.width = "1px";
7713         }
7714         
7715         OpenLayers.Element.addClass(this.div, 'olMap');
7716
7717         // the viewPortDiv is the outermost div we modify
7718         var id = this.id + "_OpenLayers_ViewPort";
7719         this.viewPortDiv = OpenLayers.Util.createDiv(id, null, null, null,
7720                                                      "relative", null,
7721                                                      "hidden");
7722         this.viewPortDiv.style.width = "100%";
7723         this.viewPortDiv.style.height = "100%";
7724         this.viewPortDiv.className = "olMapViewport";
7725         this.div.appendChild(this.viewPortDiv);
7726
7727         this.events = new OpenLayers.Events(
7728             this, this.viewPortDiv, null, this.fallThrough, 
7729             {includeXY: true}
7730         );
7731         
7732         if (OpenLayers.TileManager && this.tileManager !== null) {
7733             if (!(this.tileManager instanceof OpenLayers.TileManager)) {
7734                 this.tileManager = new OpenLayers.TileManager(this.tileManager);
7735             }
7736             this.tileManager.addMap(this);
7737         }
7738
7739         // the layerContainerDiv is the one that holds all the layers
7740         id = this.id + "_OpenLayers_Container";
7741         this.layerContainerDiv = OpenLayers.Util.createDiv(id);
7742         this.layerContainerDiv.style.zIndex=this.Z_INDEX_BASE['Popup']-1;
7743         this.layerContainerOriginPx = {x: 0, y: 0};
7744         this.applyTransform();
7745         
7746         this.viewPortDiv.appendChild(this.layerContainerDiv);
7747
7748         this.updateSize();
7749         if(this.eventListeners instanceof Object) {
7750             this.events.on(this.eventListeners);
7751         }
7752
7753         if (this.autoUpdateSize === true) {
7754             // updateSize on catching the window's resize
7755             // Note that this is ok, as updateSize() does nothing if the 
7756             // map's size has not actually changed.
7757             this.updateSizeDestroy = OpenLayers.Function.bind(this.updateSize, 
7758                 this);
7759             OpenLayers.Event.observe(window, 'resize',
7760                             this.updateSizeDestroy);
7761         }
7762         
7763         // only append link stylesheet if the theme property is set
7764         if(this.theme) {
7765             // check existing links for equivalent url
7766             var addNode = true;
7767             var nodes = document.getElementsByTagName('link');
7768             for(var i=0, len=nodes.length; i<len; ++i) {
7769                 if(OpenLayers.Util.isEquivalentUrl(nodes.item(i).href,
7770                                                    this.theme)) {
7771                     addNode = false;
7772                     break;
7773                 }
7774             }
7775             // only add a new node if one with an equivalent url hasn't already
7776             // been added
7777             if(addNode) {
7778                 var cssNode = document.createElement('link');
7779                 cssNode.setAttribute('rel', 'stylesheet');
7780                 cssNode.setAttribute('type', 'text/css');
7781                 cssNode.setAttribute('href', this.theme);
7782                 document.getElementsByTagName('head')[0].appendChild(cssNode);
7783             }
7784         }
7785         
7786         if (this.controls == null) { // default controls
7787             this.controls = [];
7788             if (OpenLayers.Control != null) { // running full or lite?
7789                 // Navigation or TouchNavigation depending on what is in build
7790                 if (OpenLayers.Control.Navigation) {
7791                     this.controls.push(new OpenLayers.Control.Navigation());
7792                 } else if (OpenLayers.Control.TouchNavigation) {
7793                     this.controls.push(new OpenLayers.Control.TouchNavigation());
7794                 }
7795                 if (OpenLayers.Control.Zoom) {
7796                     this.controls.push(new OpenLayers.Control.Zoom());
7797                 } else if (OpenLayers.Control.PanZoom) {
7798                     this.controls.push(new OpenLayers.Control.PanZoom());
7799                 }
7800
7801                 if (OpenLayers.Control.ArgParser) {
7802                     this.controls.push(new OpenLayers.Control.ArgParser());
7803                 }
7804                 if (OpenLayers.Control.Attribution) {
7805                     this.controls.push(new OpenLayers.Control.Attribution());
7806                 }
7807             }
7808         }
7809
7810         for(var i=0, len=this.controls.length; i<len; i++) {
7811             this.addControlToMap(this.controls[i]);
7812         }
7813
7814         this.popups = [];
7815
7816         this.unloadDestroy = OpenLayers.Function.bind(this.destroy, this);
7817         
7818
7819         // always call map.destroy()
7820         OpenLayers.Event.observe(window, 'unload', this.unloadDestroy);
7821         
7822         // add any initial layers
7823         if (options && options.layers) {
7824             /** 
7825              * If you have set options.center, the map center property will be
7826              * set at this point.  However, since setCenter has not been called,
7827              * addLayers gets confused.  So we delete the map center in this 
7828              * case.  Because the check below uses options.center, it will
7829              * be properly set below.
7830              */
7831             delete this.center;
7832             delete this.zoom;
7833             this.addLayers(options.layers);
7834             // set center (and optionally zoom)
7835             if (options.center && !this.getCenter()) {
7836                 // zoom can be undefined here
7837                 this.setCenter(options.center, options.zoom);
7838             }
7839         }
7840
7841         if (this.panMethod) {
7842             this.panTween = new OpenLayers.Tween(this.panMethod);
7843         }
7844         if (this.zoomMethod && this.applyTransform.transform) {
7845             this.zoomTween = new OpenLayers.Tween(this.zoomMethod);
7846         }
7847     },
7848
7849     /** 
7850      * APIMethod: getViewport
7851      * Get the DOMElement representing the view port.
7852      *
7853      * Returns:
7854      * {DOMElement}
7855      */
7856     getViewport: function() {
7857         return this.viewPortDiv;
7858     },
7859     
7860     /**
7861      * APIMethod: render
7862      * Render the map to a specified container.
7863      * 
7864      * Parameters:
7865      * div - {String|DOMElement} The container that the map should be rendered
7866      *     to. If different than the current container, the map viewport
7867      *     will be moved from the current to the new container.
7868      */
7869     render: function(div) {
7870         this.div = OpenLayers.Util.getElement(div);
7871         OpenLayers.Element.addClass(this.div, 'olMap');
7872         this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);
7873         this.div.appendChild(this.viewPortDiv);
7874         this.updateSize();
7875     },
7876
7877     /**
7878      * Method: unloadDestroy
7879      * Function that is called to destroy the map on page unload. stored here
7880      *     so that if map is manually destroyed, we can unregister this.
7881      */
7882     unloadDestroy: null,
7883     
7884     /**
7885      * Method: updateSizeDestroy
7886      * When the map is destroyed, we need to stop listening to updateSize
7887      *    events: this method stores the function we need to unregister in 
7888      *    non-IE browsers.
7889      */
7890     updateSizeDestroy: null,
7891
7892     /**
7893      * APIMethod: destroy
7894      * Destroy this map.
7895      *    Note that if you are using an application which removes a container
7896      *    of the map from the DOM, you need to ensure that you destroy the
7897      *    map *before* this happens; otherwise, the page unload handler
7898      *    will fail because the DOM elements that map.destroy() wants
7899      *    to clean up will be gone. (See 
7900      *    http://trac.osgeo.org/openlayers/ticket/2277 for more information).
7901      *    This will apply to GeoExt and also to other applications which
7902      *    modify the DOM of the container of the OpenLayers Map.
7903      */
7904     destroy:function() {
7905         // if unloadDestroy is null, we've already been destroyed
7906         if (!this.unloadDestroy) {
7907             return false;
7908         }
7909         
7910         // make sure panning doesn't continue after destruction
7911         if(this.panTween) {
7912             this.panTween.stop();
7913             this.panTween = null;
7914         }
7915         // make sure zooming doesn't continue after destruction
7916         if(this.zoomTween) {
7917             this.zoomTween.stop();
7918             this.zoomTween = null;
7919         }
7920
7921         // map has been destroyed. dont do it again!
7922         OpenLayers.Event.stopObserving(window, 'unload', this.unloadDestroy);
7923         this.unloadDestroy = null;
7924
7925         if (this.updateSizeDestroy) {
7926             OpenLayers.Event.stopObserving(window, 'resize', 
7927                                            this.updateSizeDestroy);
7928         }
7929         
7930         this.paddingForPopups = null;    
7931
7932         if (this.controls != null) {
7933             for (var i = this.controls.length - 1; i>=0; --i) {
7934                 this.controls[i].destroy();
7935             } 
7936             this.controls = null;
7937         }
7938         if (this.layers != null) {
7939             for (var i = this.layers.length - 1; i>=0; --i) {
7940                 //pass 'false' to destroy so that map wont try to set a new 
7941                 // baselayer after each baselayer is removed
7942                 this.layers[i].destroy(false);
7943             } 
7944             this.layers = null;
7945         }
7946         if (this.viewPortDiv && this.viewPortDiv.parentNode) {
7947             this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);
7948         }
7949         this.viewPortDiv = null;
7950         
7951         if (this.tileManager) {
7952             this.tileManager.removeMap(this);
7953             this.tileManager = null;
7954         }
7955
7956         if(this.eventListeners) {
7957             this.events.un(this.eventListeners);
7958             this.eventListeners = null;
7959         }
7960         this.events.destroy();
7961         this.events = null;
7962
7963         this.options = null;
7964     },
7965
7966     /**
7967      * APIMethod: setOptions
7968      * Change the map options
7969      *
7970      * Parameters:
7971      * options - {Object} Hashtable of options to tag to the map
7972      */
7973     setOptions: function(options) {
7974         var updatePxExtent = this.minPx &&
7975             options.restrictedExtent != this.restrictedExtent;
7976         OpenLayers.Util.extend(this, options);
7977         // force recalculation of minPx and maxPx
7978         updatePxExtent && this.moveTo(this.getCachedCenter(), this.zoom, {
7979             forceZoomChange: true
7980         });
7981     },
7982
7983     /**
7984      * APIMethod: getTileSize
7985      * Get the tile size for the map
7986      *
7987      * Returns:
7988      * {<OpenLayers.Size>}
7989      */
7990      getTileSize: function() {
7991          return this.tileSize;
7992      },
7993
7994
7995     /**
7996      * APIMethod: getBy
7997      * Get a list of objects given a property and a match item.
7998      *
7999      * Parameters:
8000      * array - {String} A property on the map whose value is an array.
8001      * property - {String} A property on each item of the given array.
8002      * match - {String | Object} A string to match.  Can also be a regular
8003      *     expression literal or object.  In addition, it can be any object
8004      *     with a method named test.  For reqular expressions or other, if
8005      *     match.test(map[array][i][property]) evaluates to true, the item will
8006      *     be included in the array returned.  If no items are found, an empty
8007      *     array is returned.
8008      *
8009      * Returns:
8010      * {Array} An array of items where the given property matches the given
8011      *     criteria.
8012      */
8013     getBy: function(array, property, match) {
8014         var test = (typeof match.test == "function");
8015         var found = OpenLayers.Array.filter(this[array], function(item) {
8016             return item[property] == match || (test && match.test(item[property]));
8017         });
8018         return found;
8019     },
8020
8021     /**
8022      * APIMethod: getLayersBy
8023      * Get a list of layers with properties matching the given criteria.
8024      *
8025      * Parameters:
8026      * property - {String} A layer property to be matched.
8027      * match - {String | Object} A string to match.  Can also be a regular
8028      *     expression literal or object.  In addition, it can be any object
8029      *     with a method named test.  For reqular expressions or other, if
8030      *     match.test(layer[property]) evaluates to true, the layer will be
8031      *     included in the array returned.  If no layers are found, an empty
8032      *     array is returned.
8033      *
8034      * Returns:
8035      * {Array(<OpenLayers.Layer>)} A list of layers matching the given criteria.
8036      *     An empty array is returned if no matches are found.
8037      */
8038     getLayersBy: function(property, match) {
8039         return this.getBy("layers", property, match);
8040     },
8041
8042     /**
8043      * APIMethod: getLayersByName
8044      * Get a list of layers with names matching the given name.
8045      *
8046      * Parameters:
8047      * match - {String | Object} A layer name.  The name can also be a regular
8048      *     expression literal or object.  In addition, it can be any object
8049      *     with a method named test.  For reqular expressions or other, if
8050      *     name.test(layer.name) evaluates to true, the layer will be included
8051      *     in the list of layers returned.  If no layers are found, an empty
8052      *     array is returned.
8053      *
8054      * Returns:
8055      * {Array(<OpenLayers.Layer>)} A list of layers matching the given name.
8056      *     An empty array is returned if no matches are found.
8057      */
8058     getLayersByName: function(match) {
8059         return this.getLayersBy("name", match);
8060     },
8061
8062     /**
8063      * APIMethod: getLayersByClass
8064      * Get a list of layers of a given class (CLASS_NAME).
8065      *
8066      * Parameters:
8067      * match - {String | Object} A layer class name.  The match can also be a
8068      *     regular expression literal or object.  In addition, it can be any
8069      *     object with a method named test.  For reqular expressions or other,
8070      *     if type.test(layer.CLASS_NAME) evaluates to true, the layer will
8071      *     be included in the list of layers returned.  If no layers are
8072      *     found, an empty array is returned.
8073      *
8074      * Returns:
8075      * {Array(<OpenLayers.Layer>)} A list of layers matching the given class.
8076      *     An empty array is returned if no matches are found.
8077      */
8078     getLayersByClass: function(match) {
8079         return this.getLayersBy("CLASS_NAME", match);
8080     },
8081
8082     /**
8083      * APIMethod: getControlsBy
8084      * Get a list of controls with properties matching the given criteria.
8085      *
8086      * Parameters:
8087      * property - {String} A control property to be matched.
8088      * match - {String | Object} A string to match.  Can also be a regular
8089      *     expression literal or object.  In addition, it can be any object
8090      *     with a method named test.  For reqular expressions or other, if
8091      *     match.test(layer[property]) evaluates to true, the layer will be
8092      *     included in the array returned.  If no layers are found, an empty
8093      *     array is returned.
8094      *
8095      * Returns:
8096      * {Array(<OpenLayers.Control>)} A list of controls matching the given
8097      *     criteria.  An empty array is returned if no matches are found.
8098      */
8099     getControlsBy: function(property, match) {
8100         return this.getBy("controls", property, match);
8101     },
8102
8103     /**
8104      * APIMethod: getControlsByClass
8105      * Get a list of controls of a given class (CLASS_NAME).
8106      *
8107      * Parameters:
8108      * match - {String | Object} A control class name.  The match can also be a
8109      *     regular expression literal or object.  In addition, it can be any
8110      *     object with a method named test.  For reqular expressions or other,
8111      *     if type.test(control.CLASS_NAME) evaluates to true, the control will
8112      *     be included in the list of controls returned.  If no controls are
8113      *     found, an empty array is returned.
8114      *
8115      * Returns:
8116      * {Array(<OpenLayers.Control>)} A list of controls matching the given class.
8117      *     An empty array is returned if no matches are found.
8118      */
8119     getControlsByClass: function(match) {
8120         return this.getControlsBy("CLASS_NAME", match);
8121     },
8122
8123   /********************************************************/
8124   /*                                                      */
8125   /*                  Layer Functions                     */
8126   /*                                                      */
8127   /*     The following functions deal with adding and     */
8128   /*        removing Layers to and from the Map           */
8129   /*                                                      */
8130   /********************************************************/         
8131
8132     /**
8133      * APIMethod: getLayer
8134      * Get a layer based on its id
8135      *
8136      * Parameters:
8137      * id - {String} A layer id
8138      *
8139      * Returns:
8140      * {<OpenLayers.Layer>} The Layer with the corresponding id from the map's 
8141      *                      layer collection, or null if not found.
8142      */
8143     getLayer: function(id) {
8144         var foundLayer = null;
8145         for (var i=0, len=this.layers.length; i<len; i++) {
8146             var layer = this.layers[i];
8147             if (layer.id == id) {
8148                 foundLayer = layer;
8149                 break;
8150             }
8151         }
8152         return foundLayer;
8153     },
8154
8155     /**
8156     * Method: setLayerZIndex
8157     * 
8158     * Parameters:
8159     * layer - {<OpenLayers.Layer>} 
8160     * zIdx - {int} 
8161     */    
8162     setLayerZIndex: function (layer, zIdx) {
8163         layer.setZIndex(
8164             this.Z_INDEX_BASE[layer.isBaseLayer ? 'BaseLayer' : 'Overlay']
8165             + zIdx * 5 );
8166     },
8167
8168     /**
8169      * Method: resetLayersZIndex
8170      * Reset each layer's z-index based on layer's array index
8171      */
8172     resetLayersZIndex: function() {
8173         for (var i=0, len=this.layers.length; i<len; i++) {
8174             var layer = this.layers[i];
8175             this.setLayerZIndex(layer, i);
8176         }
8177     },
8178
8179     /**
8180     * APIMethod: addLayer
8181     *
8182     * Parameters:
8183     * layer - {<OpenLayers.Layer>} 
8184     *
8185     * Returns:
8186     * {Boolean} True if the layer has been added to the map.
8187     */    
8188     addLayer: function (layer) {
8189         for(var i = 0, len = this.layers.length; i < len; i++) {
8190             if (this.layers[i] == layer) {
8191                 return false;
8192             }
8193         }
8194         if (this.events.triggerEvent("preaddlayer", {layer: layer}) === false) {
8195             return false;
8196         }
8197         if(this.allOverlays) {
8198             layer.isBaseLayer = false;
8199         }
8200         
8201         layer.div.className = "olLayerDiv";
8202         layer.div.style.overflow = "";
8203         this.setLayerZIndex(layer, this.layers.length);
8204
8205         if (layer.isFixed) {
8206             this.viewPortDiv.appendChild(layer.div);
8207         } else {
8208             this.layerContainerDiv.appendChild(layer.div);
8209         }
8210         this.layers.push(layer);
8211         layer.setMap(this);
8212
8213         if (layer.isBaseLayer || (this.allOverlays && !this.baseLayer))  {
8214             if (this.baseLayer == null) {
8215                 // set the first baselaye we add as the baselayer
8216                 this.setBaseLayer(layer);
8217             } else {
8218                 layer.setVisibility(false);
8219             }
8220         } else {
8221             layer.redraw();
8222         }
8223
8224         this.events.triggerEvent("addlayer", {layer: layer});
8225         layer.events.triggerEvent("added", {map: this, layer: layer});
8226         layer.afterAdd();
8227
8228         return true;
8229     },
8230
8231     /**
8232     * APIMethod: addLayers 
8233     *
8234     * Parameters:
8235     * layers - {Array(<OpenLayers.Layer>)} 
8236     */    
8237     addLayers: function (layers) {
8238         for (var i=0, len=layers.length; i<len; i++) {
8239             this.addLayer(layers[i]);
8240         }
8241     },
8242
8243     /** 
8244      * APIMethod: removeLayer
8245      * Removes a layer from the map by removing its visual element (the 
8246      *   layer.div property), then removing it from the map's internal list 
8247      *   of layers, setting the layer's map property to null. 
8248      * 
8249      *   a "removelayer" event is triggered.
8250      * 
8251      *   very worthy of mention is that simply removing a layer from a map
8252      *   will not cause the removal of any popups which may have been created
8253      *   by the layer. this is due to the fact that it was decided at some
8254      *   point that popups would not belong to layers. thus there is no way 
8255      *   for us to know here to which layer the popup belongs.
8256      *    
8257      *     A simple solution to this is simply to call destroy() on the layer.
8258      *     the default OpenLayers.Layer class's destroy() function
8259      *     automatically takes care to remove itself from whatever map it has
8260      *     been attached to. 
8261      * 
8262      *     The correct solution is for the layer itself to register an 
8263      *     event-handler on "removelayer" and when it is called, if it 
8264      *     recognizes itself as the layer being removed, then it cycles through
8265      *     its own personal list of popups, removing them from the map.
8266      * 
8267      * Parameters:
8268      * layer - {<OpenLayers.Layer>} 
8269      * setNewBaseLayer - {Boolean} Default is true
8270      */
8271     removeLayer: function(layer, setNewBaseLayer) {
8272         if (this.events.triggerEvent("preremovelayer", {layer: layer}) === false) {
8273             return;
8274         }
8275         if (setNewBaseLayer == null) {
8276             setNewBaseLayer = true;
8277         }
8278
8279         if (layer.isFixed) {
8280             this.viewPortDiv.removeChild(layer.div);
8281         } else {
8282             this.layerContainerDiv.removeChild(layer.div);
8283         }
8284         OpenLayers.Util.removeItem(this.layers, layer);
8285         layer.removeMap(this);
8286         layer.map = null;
8287
8288         // if we removed the base layer, need to set a new one
8289         if(this.baseLayer == layer) {
8290             this.baseLayer = null;
8291             if(setNewBaseLayer) {
8292                 for(var i=0, len=this.layers.length; i<len; i++) {
8293                     var iLayer = this.layers[i];
8294                     if (iLayer.isBaseLayer || this.allOverlays) {
8295                         this.setBaseLayer(iLayer);
8296                         break;
8297                     }
8298                 }
8299             }
8300         }
8301
8302         this.resetLayersZIndex();
8303
8304         this.events.triggerEvent("removelayer", {layer: layer});
8305         layer.events.triggerEvent("removed", {map: this, layer: layer});
8306     },
8307
8308     /**
8309      * APIMethod: getNumLayers
8310      * 
8311      * Returns:
8312      * {Int} The number of layers attached to the map.
8313      */
8314     getNumLayers: function () {
8315         return this.layers.length;
8316     },
8317
8318     /** 
8319      * APIMethod: getLayerIndex
8320      *
8321      * Parameters:
8322      * layer - {<OpenLayers.Layer>}
8323      *
8324      * Returns:
8325      * {Integer} The current (zero-based) index of the given layer in the map's
8326      *           layer stack. Returns -1 if the layer isn't on the map.
8327      */
8328     getLayerIndex: function (layer) {
8329         return OpenLayers.Util.indexOf(this.layers, layer);
8330     },
8331     
8332     /** 
8333      * APIMethod: setLayerIndex
8334      * Move the given layer to the specified (zero-based) index in the layer
8335      *     list, changing its z-index in the map display. Use
8336      *     map.getLayerIndex() to find out the current index of a layer. Note
8337      *     that this cannot (or at least should not) be effectively used to
8338      *     raise base layers above overlays.
8339      *
8340      * Parameters:
8341      * layer - {<OpenLayers.Layer>} 
8342      * idx - {int} 
8343      */
8344     setLayerIndex: function (layer, idx) {
8345         var base = this.getLayerIndex(layer);
8346         if (idx < 0) {
8347             idx = 0;
8348         } else if (idx > this.layers.length) {
8349             idx = this.layers.length;
8350         }
8351         if (base != idx) {
8352             this.layers.splice(base, 1);
8353             this.layers.splice(idx, 0, layer);
8354             for (var i=0, len=this.layers.length; i<len; i++) {
8355                 this.setLayerZIndex(this.layers[i], i);
8356             }
8357             this.events.triggerEvent("changelayer", {
8358                 layer: layer, property: "order"
8359             });
8360             if(this.allOverlays) {
8361                 if(idx === 0) {
8362                     this.setBaseLayer(layer);
8363                 } else if(this.baseLayer !== this.layers[0]) {
8364                     this.setBaseLayer(this.layers[0]);
8365                 }
8366             }
8367         }
8368     },
8369
8370     /** 
8371      * APIMethod: raiseLayer
8372      * Change the index of the given layer by delta. If delta is positive, 
8373      *     the layer is moved up the map's layer stack; if delta is negative,
8374      *     the layer is moved down.  Again, note that this cannot (or at least
8375      *     should not) be effectively used to raise base layers above overlays.
8376      *
8377      * Parameters:
8378      * layer - {<OpenLayers.Layer>} 
8379      * delta - {int} 
8380      */
8381     raiseLayer: function (layer, delta) {
8382         var idx = this.getLayerIndex(layer) + delta;
8383         this.setLayerIndex(layer, idx);
8384     },
8385     
8386     /** 
8387      * APIMethod: setBaseLayer
8388      * Allows user to specify one of the currently-loaded layers as the Map's
8389      *     new base layer.
8390      * 
8391      * Parameters:
8392      * newBaseLayer - {<OpenLayers.Layer>}
8393      */
8394     setBaseLayer: function(newBaseLayer) {
8395         
8396         if (newBaseLayer != this.baseLayer) {
8397           
8398             // ensure newBaseLayer is already loaded
8399             if (OpenLayers.Util.indexOf(this.layers, newBaseLayer) != -1) {
8400
8401                 // preserve center and scale when changing base layers
8402                 var center = this.getCachedCenter();
8403                 var oldResolution = this.getResolution();
8404                 var newResolution = OpenLayers.Util.getResolutionFromScale(
8405                     this.getScale(), newBaseLayer.units
8406                 );
8407
8408                 // make the old base layer invisible 
8409                 if (this.baseLayer != null && !this.allOverlays) {
8410                     this.baseLayer.setVisibility(false);
8411                 }
8412
8413                 // set new baselayer
8414                 this.baseLayer = newBaseLayer;
8415                 
8416                 if(!this.allOverlays || this.baseLayer.visibility) {
8417                     this.baseLayer.setVisibility(true);
8418                     // Layer may previously have been visible but not in range.
8419                     // In this case we need to redraw it to make it visible.
8420                     if (this.baseLayer.inRange === false) {
8421                         this.baseLayer.redraw();
8422                     }
8423                 }
8424
8425                 // recenter the map
8426                 if (center != null) {
8427                     // new zoom level derived from old scale
8428                     var newZoom = this.getZoomForResolution(
8429                         newResolution || this.resolution, true
8430                     );
8431                     // zoom and force zoom change
8432                     this.setCenter(center, newZoom, false, oldResolution != newResolution);
8433                 }
8434
8435                 this.events.triggerEvent("changebaselayer", {
8436                     layer: this.baseLayer
8437                 });
8438             }        
8439         }
8440     },
8441
8442
8443   /********************************************************/
8444   /*                                                      */
8445   /*                 Control Functions                    */
8446   /*                                                      */
8447   /*     The following functions deal with adding and     */
8448   /*        removing Controls to and from the Map         */
8449   /*                                                      */
8450   /********************************************************/         
8451
8452     /**
8453      * APIMethod: addControl
8454      * Add the passed over control to the map. Optionally 
8455      *     position the control at the given pixel.
8456      * 
8457      * Parameters:
8458      * control - {<OpenLayers.Control>}
8459      * px - {<OpenLayers.Pixel>}
8460      */    
8461     addControl: function (control, px) {
8462         this.controls.push(control);
8463         this.addControlToMap(control, px);
8464     },
8465     
8466     /**
8467      * APIMethod: addControls
8468      * Add all of the passed over controls to the map. 
8469      *     You can pass over an optional second array
8470      *     with pixel-objects to position the controls.
8471      *     The indices of the two arrays should match and
8472      *     you can add null as pixel for those controls 
8473      *     you want to be autopositioned.   
8474      *     
8475      * Parameters:
8476      * controls - {Array(<OpenLayers.Control>)}
8477      * pixels - {Array(<OpenLayers.Pixel>)}
8478      */    
8479     addControls: function (controls, pixels) {
8480         var pxs = (arguments.length === 1) ? [] : pixels;
8481         for (var i=0, len=controls.length; i<len; i++) {
8482             var ctrl = controls[i];
8483             var px = (pxs[i]) ? pxs[i] : null;
8484             this.addControl( ctrl, px );
8485         }
8486     },
8487
8488     /**
8489      * Method: addControlToMap
8490      * 
8491      * Parameters:
8492      * 
8493      * control - {<OpenLayers.Control>}
8494      * px - {<OpenLayers.Pixel>}
8495      */    
8496     addControlToMap: function (control, px) {
8497         // If a control doesn't have a div at this point, it belongs in the
8498         // viewport.
8499         control.outsideViewport = (control.div != null);
8500         
8501         // If the map has a displayProjection, and the control doesn't, set 
8502         // the display projection.
8503         if (this.displayProjection && !control.displayProjection) {
8504             control.displayProjection = this.displayProjection;
8505         }    
8506         
8507         control.setMap(this);
8508         var div = control.draw(px);
8509         if (div) {
8510             if(!control.outsideViewport) {
8511                 div.style.zIndex = this.Z_INDEX_BASE['Control'] +
8512                                     this.controls.length;
8513                 this.viewPortDiv.appendChild( div );
8514             }
8515         }
8516         if(control.autoActivate) {
8517             control.activate();
8518         }
8519     },
8520     
8521     /**
8522      * APIMethod: getControl
8523      * 
8524      * Parameters:
8525      * id - {String} ID of the control to return.
8526      * 
8527      * Returns:
8528      * {<OpenLayers.Control>} The control from the map's list of controls 
8529      *                        which has a matching 'id'. If none found, 
8530      *                        returns null.
8531      */    
8532     getControl: function (id) {
8533         var returnControl = null;
8534         for(var i=0, len=this.controls.length; i<len; i++) {
8535             var control = this.controls[i];
8536             if (control.id == id) {
8537                 returnControl = control;
8538                 break;
8539             }
8540         }
8541         return returnControl;
8542     },
8543     
8544     /** 
8545      * APIMethod: removeControl
8546      * Remove a control from the map. Removes the control both from the map 
8547      *     object's internal array of controls, as well as from the map's 
8548      *     viewPort (assuming the control was not added outsideViewport)
8549      * 
8550      * Parameters:
8551      * control - {<OpenLayers.Control>} The control to remove.
8552      */    
8553     removeControl: function (control) {
8554         //make sure control is non-null and actually part of our map
8555         if ( (control) && (control == this.getControl(control.id)) ) {
8556             if (control.div && (control.div.parentNode == this.viewPortDiv)) {
8557                 this.viewPortDiv.removeChild(control.div);
8558             }
8559             OpenLayers.Util.removeItem(this.controls, control);
8560         }
8561     },
8562
8563   /********************************************************/
8564   /*                                                      */
8565   /*                  Popup Functions                     */
8566   /*                                                      */
8567   /*     The following functions deal with adding and     */
8568   /*        removing Popups to and from the Map           */
8569   /*                                                      */
8570   /********************************************************/         
8571
8572     /** 
8573      * APIMethod: addPopup
8574      * 
8575      * Parameters:
8576      * popup - {<OpenLayers.Popup>}
8577      * exclusive - {Boolean} If true, closes all other popups first
8578      */
8579     addPopup: function(popup, exclusive) {
8580
8581         if (exclusive) {
8582             //remove all other popups from screen
8583             for (var i = this.popups.length - 1; i >= 0; --i) {
8584                 this.removePopup(this.popups[i]);
8585             }
8586         }
8587
8588         popup.map = this;
8589         this.popups.push(popup);
8590         var popupDiv = popup.draw();
8591         if (popupDiv) {
8592             popupDiv.style.zIndex = this.Z_INDEX_BASE['Popup'] +
8593                                     this.popups.length;
8594             this.layerContainerDiv.appendChild(popupDiv);
8595         }
8596     },
8597     
8598     /** 
8599     * APIMethod: removePopup
8600     * 
8601     * Parameters:
8602     * popup - {<OpenLayers.Popup>}
8603     */
8604     removePopup: function(popup) {
8605         OpenLayers.Util.removeItem(this.popups, popup);
8606         if (popup.div) {
8607             try { this.layerContainerDiv.removeChild(popup.div); }
8608             catch (e) { } // Popups sometimes apparently get disconnected
8609                       // from the layerContainerDiv, and cause complaints.
8610         }
8611         popup.map = null;
8612     },
8613
8614   /********************************************************/
8615   /*                                                      */
8616   /*              Container Div Functions                 */
8617   /*                                                      */
8618   /*   The following functions deal with the access to    */
8619   /*    and maintenance of the size of the container div  */
8620   /*                                                      */
8621   /********************************************************/     
8622
8623     /**
8624      * APIMethod: getSize
8625      * 
8626      * Returns:
8627      * {<OpenLayers.Size>} An <OpenLayers.Size> object that represents the 
8628      *                     size, in pixels, of the div into which OpenLayers 
8629      *                     has been loaded. 
8630      *                     Note - A clone() of this locally cached variable is
8631      *                     returned, so as not to allow users to modify it.
8632      */
8633     getSize: function () {
8634         var size = null;
8635         if (this.size != null) {
8636             size = this.size.clone();
8637         }
8638         return size;
8639     },
8640
8641     /**
8642      * APIMethod: updateSize
8643      * This function should be called by any external code which dynamically
8644      *     changes the size of the map div (because mozilla wont let us catch 
8645      *     the "onresize" for an element)
8646      */
8647     updateSize: function() {
8648         // the div might have moved on the page, also
8649         var newSize = this.getCurrentSize();
8650         if (newSize && !isNaN(newSize.h) && !isNaN(newSize.w)) {
8651             this.events.clearMouseCache();
8652             var oldSize = this.getSize();
8653             if (oldSize == null) {
8654                 this.size = oldSize = newSize;
8655             }
8656             if (!newSize.equals(oldSize)) {
8657                 
8658                 // store the new size
8659                 this.size = newSize;
8660     
8661                 //notify layers of mapresize
8662                 for(var i=0, len=this.layers.length; i<len; i++) {
8663                     this.layers[i].onMapResize();                
8664                 }
8665     
8666                 var center = this.getCachedCenter();
8667     
8668                 if (this.baseLayer != null && center != null) {
8669                     var zoom = this.getZoom();
8670                     this.zoom = null;
8671                     this.setCenter(center, zoom);
8672                 }
8673     
8674             }
8675         }
8676         this.events.triggerEvent("updatesize");
8677     },
8678     
8679     /**
8680      * Method: getCurrentSize
8681      * 
8682      * Returns:
8683      * {<OpenLayers.Size>} A new <OpenLayers.Size> object with the dimensions 
8684      *                     of the map div
8685      */
8686     getCurrentSize: function() {
8687
8688         var size = new OpenLayers.Size(this.div.clientWidth, 
8689                                        this.div.clientHeight);
8690
8691         if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) {
8692             size.w = this.div.offsetWidth;
8693             size.h = this.div.offsetHeight;
8694         }
8695         if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) {
8696             size.w = parseInt(this.div.style.width);
8697             size.h = parseInt(this.div.style.height);
8698         }
8699         return size;
8700     },
8701
8702     /** 
8703      * Method: calculateBounds
8704      * 
8705      * Parameters:
8706      * center - {<OpenLayers.LonLat>} Default is this.getCenter()
8707      * resolution - {float} Default is this.getResolution() 
8708      * 
8709      * Returns:
8710      * {<OpenLayers.Bounds>} A bounds based on resolution, center, and 
8711      *                       current mapsize.
8712      */
8713     calculateBounds: function(center, resolution) {
8714
8715         var extent = null;
8716         
8717         if (center == null) {
8718             center = this.getCachedCenter();
8719         }                
8720         if (resolution == null) {
8721             resolution = this.getResolution();
8722         }
8723     
8724         if ((center != null) && (resolution != null)) {
8725             var halfWDeg = (this.size.w * resolution) / 2;
8726             var halfHDeg = (this.size.h * resolution) / 2;
8727         
8728             extent = new OpenLayers.Bounds(center.lon - halfWDeg,
8729                                            center.lat - halfHDeg,
8730                                            center.lon + halfWDeg,
8731                                            center.lat + halfHDeg);
8732         }
8733
8734         return extent;
8735     },
8736
8737
8738   /********************************************************/
8739   /*                                                      */
8740   /*            Zoom, Center, Pan Functions               */
8741   /*                                                      */
8742   /*    The following functions handle the validation,    */
8743   /*   getting and setting of the Zoom Level and Center   */
8744   /*       as well as the panning of the Map              */
8745   /*                                                      */
8746   /********************************************************/
8747     /**
8748      * APIMethod: getCenter
8749      * 
8750      * Returns:
8751      * {<OpenLayers.LonLat>}
8752      */
8753     getCenter: function () {
8754         var center = null;
8755         var cachedCenter = this.getCachedCenter();
8756         if (cachedCenter) {
8757             center = cachedCenter.clone();
8758         }
8759         return center;
8760     },
8761
8762     /**
8763      * Method: getCachedCenter
8764      *
8765      * Returns:
8766      * {<OpenLayers.LonLat>}
8767      */
8768     getCachedCenter: function() {
8769         if (!this.center && this.size) {
8770             this.center = this.getLonLatFromViewPortPx({
8771                 x: this.size.w / 2,
8772                 y: this.size.h / 2
8773             });
8774         }
8775         return this.center;
8776     },
8777
8778     /**
8779      * APIMethod: getZoom
8780      * 
8781      * Returns:
8782      * {Integer}
8783      */
8784     getZoom: function () {
8785         return this.zoom;
8786     },
8787     
8788     /** 
8789      * APIMethod: pan
8790      * Allows user to pan by a value of screen pixels
8791      * 
8792      * Parameters:
8793      * dx - {Integer}
8794      * dy - {Integer}
8795      * options - {Object} Options to configure panning:
8796      *  - *animate* {Boolean} Use panTo instead of setCenter. Default is true.
8797      *  - *dragging* {Boolean} Call setCenter with dragging true.  Default is
8798      *    false.
8799      */
8800     pan: function(dx, dy, options) {
8801         options = OpenLayers.Util.applyDefaults(options, {
8802             animate: true,
8803             dragging: false
8804         });
8805         if (options.dragging) {
8806             if (dx != 0 || dy != 0) {
8807                 this.moveByPx(dx, dy);
8808             }
8809         } else {
8810             // getCenter
8811             var centerPx = this.getViewPortPxFromLonLat(this.getCachedCenter());
8812
8813             // adjust
8814             var newCenterPx = centerPx.add(dx, dy);
8815
8816             if (this.dragging || !newCenterPx.equals(centerPx)) {
8817                 var newCenterLonLat = this.getLonLatFromViewPortPx(newCenterPx);
8818                 if (options.animate) {
8819                     this.panTo(newCenterLonLat);
8820                 } else {
8821                     this.moveTo(newCenterLonLat);
8822                     if(this.dragging) {
8823                         this.dragging = false;
8824                         this.events.triggerEvent("moveend");
8825                     }
8826                 }    
8827             }
8828         }        
8829
8830    },
8831    
8832    /** 
8833      * APIMethod: panTo
8834      * Allows user to pan to a new lonlat.
8835      * If the new lonlat is in the current extent the map will slide smoothly
8836      * 
8837      * Parameters:
8838      * lonlat - {<OpenLayers.LonLat>}
8839      */
8840     panTo: function(lonlat) {
8841         if (this.panTween && this.getExtent().scale(this.panRatio).containsLonLat(lonlat)) {
8842             var center = this.getCachedCenter();
8843
8844             // center will not change, don't do nothing
8845             if (lonlat.equals(center)) {
8846                 return;
8847             }
8848
8849             var from = this.getPixelFromLonLat(center);
8850             var to = this.getPixelFromLonLat(lonlat);
8851             var vector = { x: to.x - from.x, y: to.y - from.y };
8852             var last = { x: 0, y: 0 };
8853
8854             this.panTween.start( { x: 0, y: 0 }, vector, this.panDuration, {
8855                 callbacks: {
8856                     eachStep: OpenLayers.Function.bind(function(px) {
8857                         var x = px.x - last.x,
8858                             y = px.y - last.y;
8859                         this.moveByPx(x, y);
8860                         last.x = Math.round(px.x);
8861                         last.y = Math.round(px.y);
8862                     }, this),
8863                     done: OpenLayers.Function.bind(function(px) {
8864                         this.moveTo(lonlat);
8865                         this.dragging = false;
8866                         this.events.triggerEvent("moveend");
8867                     }, this)
8868                 }
8869             });
8870         } else {
8871             this.setCenter(lonlat);
8872         }
8873     },
8874
8875     /**
8876      * APIMethod: setCenter
8877      * Set the map center (and optionally, the zoom level).
8878      * 
8879      * Parameters:
8880      * lonlat - {<OpenLayers.LonLat>|Array} The new center location.
8881      *     If provided as array, the first value is the x coordinate,
8882      *     and the 2nd value is the y coordinate.
8883      * zoom - {Integer} Optional zoom level.
8884      * dragging - {Boolean} Specifies whether or not to trigger 
8885      *                      movestart/end events
8886      * forceZoomChange - {Boolean} Specifies whether or not to trigger zoom 
8887      *                             change events (needed on baseLayer change)
8888      *
8889      * TBD: reconsider forceZoomChange in 3.0
8890      */
8891     setCenter: function(lonlat, zoom, dragging, forceZoomChange) {
8892         if (this.panTween) {
8893             this.panTween.stop();
8894         }
8895         if (this.zoomTween) {
8896             this.zoomTween.stop();
8897         }            
8898         this.moveTo(lonlat, zoom, {
8899             'dragging': dragging,
8900             'forceZoomChange': forceZoomChange
8901         });
8902     },
8903     
8904     /** 
8905      * Method: moveByPx
8906      * Drag the map by pixels.
8907      *
8908      * Parameters:
8909      * dx - {Number}
8910      * dy - {Number}
8911      */
8912     moveByPx: function(dx, dy) {
8913         var hw = this.size.w / 2;
8914         var hh = this.size.h / 2;
8915         var x = hw + dx;
8916         var y = hh + dy;
8917         var wrapDateLine = this.baseLayer.wrapDateLine;
8918         var xRestriction = 0;
8919         var yRestriction = 0;
8920         if (this.restrictedExtent) {
8921             xRestriction = hw;
8922             yRestriction = hh;
8923             // wrapping the date line makes no sense for restricted extents
8924             wrapDateLine = false;
8925         }
8926         dx = wrapDateLine ||
8927                     x <= this.maxPx.x - xRestriction &&
8928                     x >= this.minPx.x + xRestriction ? Math.round(dx) : 0;
8929         dy = y <= this.maxPx.y - yRestriction &&
8930                     y >= this.minPx.y + yRestriction ? Math.round(dy) : 0;
8931         if (dx || dy) {
8932             if (!this.dragging) {
8933                 this.dragging = true;
8934                 this.events.triggerEvent("movestart");
8935             }
8936             this.center = null;
8937             if (dx) {
8938                 this.layerContainerOriginPx.x -= dx;
8939                 this.minPx.x -= dx;
8940                 this.maxPx.x -= dx;
8941             }
8942             if (dy) {
8943                 this.layerContainerOriginPx.y -= dy;
8944                 this.minPx.y -= dy;
8945                 this.maxPx.y -= dy;
8946             }
8947             this.applyTransform();
8948             var layer, i, len;
8949             for (i=0, len=this.layers.length; i<len; ++i) {
8950                 layer = this.layers[i];
8951                 if (layer.visibility &&
8952                     (layer === this.baseLayer || layer.inRange)) {
8953                     layer.moveByPx(dx, dy);
8954                     layer.events.triggerEvent("move");
8955                 }
8956             }
8957             this.events.triggerEvent("move");
8958         }
8959     },
8960     
8961     /**
8962      * Method: adjustZoom
8963      *
8964      * Parameters:
8965      * zoom - {Number} The zoom level to adjust
8966      *
8967      * Returns:
8968      * {Integer} Adjusted zoom level that shows a map not wider than its
8969      * <baseLayer>'s maxExtent.
8970      */
8971     adjustZoom: function(zoom) {
8972         if (this.baseLayer && this.baseLayer.wrapDateLine) {
8973             var resolution, resolutions = this.baseLayer.resolutions,
8974                 maxResolution = this.getMaxExtent().getWidth() / this.size.w;
8975             if (this.getResolutionForZoom(zoom) > maxResolution) {
8976                 if (this.fractionalZoom) {
8977                     zoom = this.getZoomForResolution(maxResolution);
8978                 } else {
8979                     for (var i=zoom|0, ii=resolutions.length; i<ii; ++i) {
8980                         if (resolutions[i] <= maxResolution) {
8981                             zoom = i;
8982                             break;
8983                         }
8984                     }
8985                 } 
8986             }
8987         }
8988         return zoom;
8989     },
8990     
8991     /**
8992      * APIMethod: getMinZoom
8993      * Returns the minimum zoom level for the current map view. If the base
8994      * layer is configured with <wrapDateLine> set to true, this will be the
8995      * first zoom level that shows no more than one world width in the current
8996      * map viewport. Components that rely on this value (e.g. zoom sliders)
8997      * should also listen to the map's "updatesize" event and call this method
8998      * in the "updatesize" listener.
8999      *
9000      * Returns:
9001      * {Number} Minimum zoom level that shows a map not wider than its
9002      * <baseLayer>'s maxExtent. This is an Integer value, unless the map is
9003      * configured with <fractionalZoom> set to true.
9004      */
9005     getMinZoom: function() {
9006         return this.adjustZoom(0);
9007     },
9008
9009     /**
9010      * Method: moveTo
9011      *
9012      * Parameters:
9013      * lonlat - {<OpenLayers.LonLat>}
9014      * zoom - {Integer}
9015      * options - {Object}
9016      */
9017     moveTo: function(lonlat, zoom, options) {
9018         if (lonlat != null && !(lonlat instanceof OpenLayers.LonLat)) {
9019             lonlat = new OpenLayers.LonLat(lonlat);
9020         }
9021         if (!options) { 
9022             options = {};
9023         }
9024         if (zoom != null) {
9025             zoom = parseFloat(zoom);
9026             if (!this.fractionalZoom) {
9027                 zoom = Math.round(zoom);
9028             }
9029         }
9030         var requestedZoom = zoom;
9031         zoom = this.adjustZoom(zoom);
9032         if (zoom !== requestedZoom) {
9033             // zoom was adjusted, so keep old lonlat to avoid panning
9034             lonlat = this.getCenter();
9035         }
9036         // dragging is false by default
9037         var dragging = options.dragging || this.dragging;
9038         // forceZoomChange is false by default
9039         var forceZoomChange = options.forceZoomChange;
9040
9041         if (!this.getCachedCenter() && !this.isValidLonLat(lonlat)) {
9042             lonlat = this.maxExtent.getCenterLonLat();
9043             this.center = lonlat.clone();
9044         }
9045
9046         if(this.restrictedExtent != null) {
9047             // In 3.0, decide if we want to change interpretation of maxExtent.
9048             if(lonlat == null) { 
9049                 lonlat = this.center; 
9050             }
9051             if(zoom == null) { 
9052                 zoom = this.getZoom(); 
9053             }
9054             var resolution = this.getResolutionForZoom(zoom);
9055             var extent = this.calculateBounds(lonlat, resolution); 
9056             if(!this.restrictedExtent.containsBounds(extent)) {
9057                 var maxCenter = this.restrictedExtent.getCenterLonLat(); 
9058                 if(extent.getWidth() > this.restrictedExtent.getWidth()) { 
9059                     lonlat = new OpenLayers.LonLat(maxCenter.lon, lonlat.lat); 
9060                 } else if(extent.left < this.restrictedExtent.left) {
9061                     lonlat = lonlat.add(this.restrictedExtent.left -
9062                                         extent.left, 0); 
9063                 } else if(extent.right > this.restrictedExtent.right) { 
9064                     lonlat = lonlat.add(this.restrictedExtent.right -
9065                                         extent.right, 0); 
9066                 } 
9067                 if(extent.getHeight() > this.restrictedExtent.getHeight()) { 
9068                     lonlat = new OpenLayers.LonLat(lonlat.lon, maxCenter.lat); 
9069                 } else if(extent.bottom < this.restrictedExtent.bottom) { 
9070                     lonlat = lonlat.add(0, this.restrictedExtent.bottom -
9071                                         extent.bottom); 
9072                 } 
9073                 else if(extent.top > this.restrictedExtent.top) { 
9074                     lonlat = lonlat.add(0, this.restrictedExtent.top -
9075                                         extent.top); 
9076                 } 
9077             }
9078         }
9079         
9080         var zoomChanged = forceZoomChange || (
9081                             (this.isValidZoomLevel(zoom)) && 
9082                             (zoom != this.getZoom()) );
9083
9084         var centerChanged = (this.isValidLonLat(lonlat)) && 
9085                             (!lonlat.equals(this.center));
9086
9087         // if neither center nor zoom will change, no need to do anything
9088         if (zoomChanged || centerChanged || dragging) {
9089             dragging || this.events.triggerEvent("movestart", {
9090                 zoomChanged: zoomChanged
9091             });
9092
9093             if (centerChanged) {
9094                 if (!zoomChanged && this.center) { 
9095                     // if zoom hasn't changed, just slide layerContainer
9096                     //  (must be done before setting this.center to new value)
9097                     this.centerLayerContainer(lonlat);
9098                 }
9099                 this.center = lonlat.clone();
9100             }
9101
9102             var res = zoomChanged ?
9103                 this.getResolutionForZoom(zoom) : this.getResolution();
9104             // (re)set the layerContainerDiv's location
9105             if (zoomChanged || this.layerContainerOrigin == null) {
9106                 this.layerContainerOrigin = this.getCachedCenter();
9107                 this.layerContainerOriginPx.x = 0;
9108                 this.layerContainerOriginPx.y = 0;
9109                 this.applyTransform();
9110                 var maxExtent = this.getMaxExtent({restricted: true});
9111                 var maxExtentCenter = maxExtent.getCenterLonLat();
9112                 var lonDelta = this.center.lon - maxExtentCenter.lon;
9113                 var latDelta = maxExtentCenter.lat - this.center.lat;
9114                 var extentWidth = Math.round(maxExtent.getWidth() / res);
9115                 var extentHeight = Math.round(maxExtent.getHeight() / res);
9116                 this.minPx = {
9117                     x: (this.size.w - extentWidth) / 2 - lonDelta / res,
9118                     y: (this.size.h - extentHeight) / 2 - latDelta / res
9119                 };
9120                 this.maxPx = {
9121                     x: this.minPx.x + Math.round(maxExtent.getWidth() / res),
9122                     y: this.minPx.y + Math.round(maxExtent.getHeight() / res)
9123                 };
9124             }
9125
9126             if (zoomChanged) {
9127                 this.zoom = zoom;
9128                 this.resolution = res;
9129             }    
9130             
9131             var bounds = this.getExtent();
9132             
9133             //send the move call to the baselayer and all the overlays    
9134
9135             if(this.baseLayer.visibility) {
9136                 this.baseLayer.moveTo(bounds, zoomChanged, options.dragging);
9137                 options.dragging || this.baseLayer.events.triggerEvent(
9138                     "moveend", {zoomChanged: zoomChanged}
9139                 );
9140             }
9141             
9142             bounds = this.baseLayer.getExtent();
9143             
9144             for (var i=this.layers.length-1; i>=0; --i) {
9145                 var layer = this.layers[i];
9146                 if (layer !== this.baseLayer && !layer.isBaseLayer) {
9147                     var inRange = layer.calculateInRange();
9148                     if (layer.inRange != inRange) {
9149                         // the inRange property has changed. If the layer is
9150                         // no longer in range, we turn it off right away. If
9151                         // the layer is no longer out of range, the moveTo
9152                         // call below will turn on the layer.
9153                         layer.inRange = inRange;
9154                         if (!inRange) {
9155                             layer.display(false);
9156                         }
9157                         this.events.triggerEvent("changelayer", {
9158                             layer: layer, property: "visibility"
9159                         });
9160                     }
9161                     if (inRange && layer.visibility) {
9162                         layer.moveTo(bounds, zoomChanged, options.dragging);
9163                         options.dragging || layer.events.triggerEvent(
9164                             "moveend", {zoomChanged: zoomChanged}
9165                         );
9166                     }
9167                 }                
9168             }
9169             
9170             this.events.triggerEvent("move");
9171             dragging || this.events.triggerEvent("moveend");
9172
9173             if (zoomChanged) {
9174                 //redraw popups
9175                 for (var i=0, len=this.popups.length; i<len; i++) {
9176                     this.popups[i].updatePosition();
9177                 }
9178                 this.events.triggerEvent("zoomend");
9179             }
9180         }
9181     },
9182
9183     /** 
9184      * Method: centerLayerContainer
9185      * This function takes care to recenter the layerContainerDiv.
9186      * 
9187      * Parameters:
9188      * lonlat - {<OpenLayers.LonLat>}
9189      */
9190     centerLayerContainer: function (lonlat) {
9191         var originPx = this.getViewPortPxFromLonLat(this.layerContainerOrigin);
9192         var newPx = this.getViewPortPxFromLonLat(lonlat);
9193
9194         if ((originPx != null) && (newPx != null)) {
9195             var oldLeft = this.layerContainerOriginPx.x;
9196             var oldTop = this.layerContainerOriginPx.y;
9197             var newLeft = Math.round(originPx.x - newPx.x);
9198             var newTop = Math.round(originPx.y - newPx.y);
9199             this.applyTransform(
9200                 (this.layerContainerOriginPx.x = newLeft),
9201                 (this.layerContainerOriginPx.y = newTop));
9202             var dx = oldLeft - newLeft;
9203             var dy = oldTop - newTop;
9204             this.minPx.x -= dx;
9205             this.maxPx.x -= dx;
9206             this.minPx.y -= dy;
9207             this.maxPx.y -= dy;
9208         }        
9209     },
9210
9211     /**
9212      * Method: isValidZoomLevel
9213      * 
9214      * Parameters:
9215      * zoomLevel - {Integer}
9216      * 
9217      * Returns:
9218      * {Boolean} Whether or not the zoom level passed in is non-null and 
9219      *           within the min/max range of zoom levels.
9220      */
9221     isValidZoomLevel: function(zoomLevel) {
9222         return ( (zoomLevel != null) &&
9223                  (zoomLevel >= 0) && 
9224                  (zoomLevel < this.getNumZoomLevels()) );
9225     },
9226     
9227     /**
9228      * Method: isValidLonLat
9229      * 
9230      * Parameters:
9231      * lonlat - {<OpenLayers.LonLat>}
9232      * 
9233      * Returns:
9234      * {Boolean} Whether or not the lonlat passed in is non-null and within
9235      *           the maxExtent bounds
9236      */
9237     isValidLonLat: function(lonlat) {
9238         var valid = false;
9239         if (lonlat != null) {
9240             var maxExtent = this.getMaxExtent();
9241             var worldBounds = this.baseLayer.wrapDateLine && maxExtent;
9242             valid = maxExtent.containsLonLat(lonlat, {worldBounds: worldBounds});
9243         }
9244         return valid;
9245     },
9246
9247   /********************************************************/
9248   /*                                                      */
9249   /*                 Layer Options                        */
9250   /*                                                      */
9251   /*    Accessor functions to Layer Options parameters    */
9252   /*                                                      */
9253   /********************************************************/
9254     
9255     /**
9256      * APIMethod: getProjection
9257      * This method returns a string representing the projection. In 
9258      *     the case of projection support, this will be the srsCode which
9259      *     is loaded -- otherwise it will simply be the string value that
9260      *     was passed to the projection at startup.
9261      *
9262      * FIXME: In 3.0, we will remove getProjectionObject, and instead
9263      *     return a Projection object from this function. 
9264      * 
9265      * Returns:
9266      * {String} The Projection string from the base layer or null. 
9267      */
9268     getProjection: function() {
9269         var projection = this.getProjectionObject();
9270         return projection ? projection.getCode() : null;
9271     },
9272     
9273     /**
9274      * APIMethod: getProjectionObject
9275      * Returns the projection obect from the baselayer.
9276      *
9277      * Returns:
9278      * {<OpenLayers.Projection>} The Projection of the base layer.
9279      */
9280     getProjectionObject: function() {
9281         var projection = null;
9282         if (this.baseLayer != null) {
9283             projection = this.baseLayer.projection;
9284         }
9285         return projection;
9286     },
9287     
9288     /**
9289      * APIMethod: getMaxResolution
9290      * 
9291      * Returns:
9292      * {String} The Map's Maximum Resolution
9293      */
9294     getMaxResolution: function() {
9295         var maxResolution = null;
9296         if (this.baseLayer != null) {
9297             maxResolution = this.baseLayer.maxResolution;
9298         }
9299         return maxResolution;
9300     },
9301         
9302     /**
9303      * APIMethod: getMaxExtent
9304      *
9305      * Parameters:
9306      * options - {Object} 
9307      * 
9308      * Allowed Options:
9309      * restricted - {Boolean} If true, returns restricted extent (if it is 
9310      *     available.)
9311      *
9312      * Returns:
9313      * {<OpenLayers.Bounds>} The maxExtent property as set on the current 
9314      *     baselayer, unless the 'restricted' option is set, in which case
9315      *     the 'restrictedExtent' option from the map is returned (if it
9316      *     is set).
9317      */
9318     getMaxExtent: function (options) {
9319         var maxExtent = null;
9320         if(options && options.restricted && this.restrictedExtent){
9321             maxExtent = this.restrictedExtent;
9322         } else if (this.baseLayer != null) {
9323             maxExtent = this.baseLayer.maxExtent;
9324         }        
9325         return maxExtent;
9326     },
9327     
9328     /**
9329      * APIMethod: getNumZoomLevels
9330      * 
9331      * Returns:
9332      * {Integer} The total number of zoom levels that can be displayed by the 
9333      *           current baseLayer.
9334      */
9335     getNumZoomLevels: function() {
9336         var numZoomLevels = null;
9337         if (this.baseLayer != null) {
9338             numZoomLevels = this.baseLayer.numZoomLevels;
9339         }
9340         return numZoomLevels;
9341     },
9342
9343   /********************************************************/
9344   /*                                                      */
9345   /*                 Baselayer Functions                  */
9346   /*                                                      */
9347   /*    The following functions, all publicly exposed     */
9348   /*       in the API?, are all merely wrappers to the    */
9349   /*       the same calls on whatever layer is set as     */
9350   /*                the current base layer                */
9351   /*                                                      */
9352   /********************************************************/
9353
9354     /**
9355      * APIMethod: getExtent
9356      * 
9357      * Returns:
9358      * {<OpenLayers.Bounds>} A Bounds object which represents the lon/lat 
9359      *                       bounds of the current viewPort. 
9360      *                       If no baselayer is set, returns null.
9361      */
9362     getExtent: function () {
9363         var extent = null;
9364         if (this.baseLayer != null) {
9365             extent = this.baseLayer.getExtent();
9366         }
9367         return extent;
9368     },
9369
9370     /**
9371      * APIMethod: getResolution
9372      * 
9373      * Returns:
9374      * {Float} The current resolution of the map. 
9375      *         If no baselayer is set, returns null.
9376      */
9377     getResolution: function () {
9378         var resolution = null;
9379         if (this.baseLayer != null) {
9380             resolution = this.baseLayer.getResolution();
9381         } else if(this.allOverlays === true && this.layers.length > 0) {
9382             // while adding the 1st layer to the map in allOverlays mode,
9383             // this.baseLayer is not set yet when we need the resolution
9384             // for calculateInRange.
9385             resolution = this.layers[0].getResolution();
9386         }
9387         return resolution;
9388     },
9389
9390     /**
9391      * APIMethod: getUnits
9392      * 
9393      * Returns:
9394      * {Float} The current units of the map. 
9395      *         If no baselayer is set, returns null.
9396      */
9397     getUnits: function () {
9398         var units = null;
9399         if (this.baseLayer != null) {
9400             units = this.baseLayer.units;
9401         }
9402         return units;
9403     },
9404
9405      /**
9406       * APIMethod: getScale
9407       * 
9408       * Returns:
9409       * {Float} The current scale denominator of the map. 
9410       *         If no baselayer is set, returns null.
9411       */
9412     getScale: function () {
9413         var scale = null;
9414         if (this.baseLayer != null) {
9415             var res = this.getResolution();
9416             var units = this.baseLayer.units;
9417             scale = OpenLayers.Util.getScaleFromResolution(res, units);
9418         }
9419         return scale;
9420     },
9421
9422
9423     /**
9424      * APIMethod: getZoomForExtent
9425      * 
9426      * Parameters: 
9427      * bounds - {<OpenLayers.Bounds>}
9428      * closest - {Boolean} Find the zoom level that most closely fits the 
9429      *     specified bounds. Note that this may result in a zoom that does 
9430      *     not exactly contain the entire extent.
9431      *     Default is false.
9432      * 
9433      * Returns:
9434      * {Integer} A suitable zoom level for the specified bounds.
9435      *           If no baselayer is set, returns null.
9436      */
9437     getZoomForExtent: function (bounds, closest) {
9438         var zoom = null;
9439         if (this.baseLayer != null) {
9440             zoom = this.baseLayer.getZoomForExtent(bounds, closest);
9441         }
9442         return zoom;
9443     },
9444
9445     /**
9446      * APIMethod: getResolutionForZoom
9447      * 
9448      * Parameters:
9449      * zoom - {Float}
9450      * 
9451      * Returns:
9452      * {Float} A suitable resolution for the specified zoom.  If no baselayer
9453      *     is set, returns null.
9454      */
9455     getResolutionForZoom: function(zoom) {
9456         var resolution = null;
9457         if(this.baseLayer) {
9458             resolution = this.baseLayer.getResolutionForZoom(zoom);
9459         }
9460         return resolution;
9461     },
9462
9463     /**
9464      * APIMethod: getZoomForResolution
9465      * 
9466      * Parameters:
9467      * resolution - {Float}
9468      * closest - {Boolean} Find the zoom level that corresponds to the absolute 
9469      *     closest resolution, which may result in a zoom whose corresponding
9470      *     resolution is actually smaller than we would have desired (if this
9471      *     is being called from a getZoomForExtent() call, then this means that
9472      *     the returned zoom index might not actually contain the entire 
9473      *     extent specified... but it'll be close).
9474      *     Default is false.
9475      * 
9476      * Returns:
9477      * {Integer} A suitable zoom level for the specified resolution.
9478      *           If no baselayer is set, returns null.
9479      */
9480     getZoomForResolution: function(resolution, closest) {
9481         var zoom = null;
9482         if (this.baseLayer != null) {
9483             zoom = this.baseLayer.getZoomForResolution(resolution, closest);
9484         }
9485         return zoom;
9486     },
9487
9488   /********************************************************/
9489   /*                                                      */
9490   /*                  Zooming Functions                   */
9491   /*                                                      */
9492   /*    The following functions, all publicly exposed     */
9493   /*       in the API, are all merely wrappers to the     */
9494   /*               the setCenter() function               */
9495   /*                                                      */
9496   /********************************************************/
9497   
9498     /** 
9499      * APIMethod: zoomTo
9500      * Zoom to a specific zoom level. Zooming will be animated unless the map
9501      * is configured with {zoomMethod: null}. To zoom without animation, use
9502      * <setCenter> without a lonlat argument.
9503      * 
9504      * Parameters:
9505      * zoom - {Integer}
9506      */
9507     zoomTo: function(zoom, xy) {
9508         // non-API arguments:
9509         // xy - {<OpenLayers.Pixel>} optional zoom origin
9510         
9511         var map = this;
9512         if (map.isValidZoomLevel(zoom)) {
9513             if (map.baseLayer.wrapDateLine) {
9514                 zoom = map.adjustZoom(zoom);
9515             }
9516             var center = xy ?
9517                 map.getZoomTargetCenter(xy, map.getResolutionForZoom(zoom)) :
9518                 map.getCenter();
9519             if (center) {
9520                 map.events.triggerEvent('zoomstart', {
9521                     center: center,
9522                     zoom: zoom
9523                 });
9524             }
9525             if (map.zoomTween) {
9526                 map.zoomTween.stop();
9527                 var currentRes = map.getResolution(),
9528                     targetRes = map.getResolutionForZoom(zoom),
9529                     start = {scale: 1},
9530                     end = {scale: currentRes / targetRes};
9531                 if (!xy) {
9532                     var size = map.getSize();
9533                     xy = {x: size.w / 2, y: size.h / 2};
9534                 }
9535                 map.zoomTween.start(start, end, map.zoomDuration, {
9536                     minFrameRate: 50, // don't spend much time zooming
9537                     callbacks: {
9538                         eachStep: function(data) {
9539                             var containerOrigin = map.layerContainerOriginPx,
9540                                 scale = data.scale,
9541                                 dx = ((scale - 1) * (containerOrigin.x - xy.x)) | 0,
9542                                 dy = ((scale - 1) * (containerOrigin.y - xy.y)) | 0;
9543                             map.applyTransform(containerOrigin.x + dx, containerOrigin.y + dy, scale);
9544                         },
9545                         done: function(data) {
9546                             map.applyTransform();
9547                             var resolution = map.getResolution() / data.scale,
9548                                 newZoom = map.getZoomForResolution(resolution, true),
9549                                 newCenter = data.scale === 1 ? center :
9550                                         map.getZoomTargetCenter(xy, resolution);
9551                             map.moveTo(newCenter, newZoom);
9552                         }
9553                     }
9554                 });
9555             } else {
9556                 map.setCenter(center, zoom);
9557             }
9558         }
9559     },
9560         
9561     /**
9562      * APIMethod: zoomIn
9563      * 
9564      */
9565     zoomIn: function() {
9566         if (this.zoomTween) {
9567             this.zoomTween.stop();
9568         }
9569         this.zoomTo(this.getZoom() + 1);
9570     },
9571     
9572     /**
9573      * APIMethod: zoomOut
9574      * 
9575      */
9576     zoomOut: function() {
9577         if (this.zoomTween) {
9578             this.zoomTween.stop();
9579         }
9580         this.zoomTo(this.getZoom() - 1);
9581     },
9582
9583     /**
9584      * APIMethod: zoomToExtent
9585      * Zoom to the passed in bounds, recenter
9586      * 
9587      * Parameters:
9588      * bounds - {<OpenLayers.Bounds>|Array} If provided as an array, the array
9589      *     should consist of four values (left, bottom, right, top).
9590      * closest - {Boolean} Find the zoom level that most closely fits the 
9591      *     specified bounds. Note that this may result in a zoom that does 
9592      *     not exactly contain the entire extent.
9593      *     Default is false.
9594      * 
9595      */
9596     zoomToExtent: function(bounds, closest) {
9597         if (!(bounds instanceof OpenLayers.Bounds)) {
9598             bounds = new OpenLayers.Bounds(bounds);
9599         }
9600         var center = bounds.getCenterLonLat();
9601         if (this.baseLayer.wrapDateLine) {
9602             var maxExtent = this.getMaxExtent();
9603
9604             //fix straddling bounds (in the case of a bbox that straddles the 
9605             // dateline, it's left and right boundaries will appear backwards. 
9606             // we fix this by allowing a right value that is greater than the
9607             // max value at the dateline -- this allows us to pass a valid 
9608             // bounds to calculate zoom)
9609             //
9610             bounds = bounds.clone();
9611             while (bounds.right < bounds.left) {
9612                 bounds.right += maxExtent.getWidth();
9613             }
9614             //if the bounds was straddling (see above), then the center point 
9615             // we got from it was wrong. So we take our new bounds and ask it
9616             // for the center.
9617             //
9618             center = bounds.getCenterLonLat().wrapDateLine(maxExtent);
9619         }
9620         this.setCenter(center, this.getZoomForExtent(bounds, closest));
9621     },
9622
9623     /** 
9624      * APIMethod: zoomToMaxExtent
9625      * Zoom to the full extent and recenter.
9626      *
9627      * Parameters:
9628      * options - {Object}
9629      * 
9630      * Allowed Options:
9631      * restricted - {Boolean} True to zoom to restricted extent if it is 
9632      *     set. Defaults to true.
9633      */
9634     zoomToMaxExtent: function(options) {
9635         //restricted is true by default
9636         var restricted = (options) ? options.restricted : true;
9637
9638         var maxExtent = this.getMaxExtent({
9639             'restricted': restricted 
9640         });
9641         this.zoomToExtent(maxExtent);
9642     },
9643
9644     /** 
9645      * APIMethod: zoomToScale
9646      * Zoom to a specified scale 
9647      * 
9648      * Parameters:
9649      * scale - {float}
9650      * closest - {Boolean} Find the zoom level that most closely fits the 
9651      *     specified scale. Note that this may result in a zoom that does 
9652      *     not exactly contain the entire extent.
9653      *     Default is false.
9654      * 
9655      */
9656     zoomToScale: function(scale, closest) {
9657         var res = OpenLayers.Util.getResolutionFromScale(scale, 
9658                                                          this.baseLayer.units);
9659
9660         var halfWDeg = (this.size.w * res) / 2;
9661         var halfHDeg = (this.size.h * res) / 2;
9662         var center = this.getCachedCenter();
9663
9664         var extent = new OpenLayers.Bounds(center.lon - halfWDeg,
9665                                            center.lat - halfHDeg,
9666                                            center.lon + halfWDeg,
9667                                            center.lat + halfHDeg);
9668         this.zoomToExtent(extent, closest);
9669     },
9670     
9671   /********************************************************/
9672   /*                                                      */
9673   /*             Translation Functions                    */
9674   /*                                                      */
9675   /*      The following functions translate between       */
9676   /*           LonLat, LayerPx, and ViewPortPx            */
9677   /*                                                      */
9678   /********************************************************/
9679       
9680   //
9681   // TRANSLATION: LonLat <-> ViewPortPx
9682   //
9683
9684     /**
9685      * Method: getLonLatFromViewPortPx
9686      * 
9687      * Parameters:
9688      * viewPortPx - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or
9689      *                                          an object with a 'x'
9690      *                                          and 'y' properties.
9691      * 
9692      * Returns:
9693      * {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in view 
9694      *                       port <OpenLayers.Pixel>, translated into lon/lat
9695      *                       by the current base layer.
9696      */
9697     getLonLatFromViewPortPx: function (viewPortPx) {
9698         var lonlat = null; 
9699         if (this.baseLayer != null) {
9700             lonlat = this.baseLayer.getLonLatFromViewPortPx(viewPortPx);
9701         }
9702         return lonlat;
9703     },
9704
9705     /**
9706      * APIMethod: getViewPortPxFromLonLat
9707      * 
9708      * Parameters:
9709      * lonlat - {<OpenLayers.LonLat>}
9710      * 
9711      * Returns:
9712      * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in 
9713      *                      <OpenLayers.LonLat>, translated into view port 
9714      *                      pixels by the current base layer.
9715      */
9716     getViewPortPxFromLonLat: function (lonlat) {
9717         var px = null; 
9718         if (this.baseLayer != null) {
9719             px = this.baseLayer.getViewPortPxFromLonLat(lonlat);
9720         }
9721         return px;
9722     },
9723
9724     /**
9725      * Method: getZoomTargetCenter
9726      *
9727      * Parameters:
9728      * xy - {<OpenLayers.Pixel>} The zoom origin pixel location on the screen
9729      * resolution - {Float} The resolution we want to get the center for
9730      *
9731      * Returns:
9732      * {<OpenLayers.LonLat>} The location of the map center after the
9733      *     transformation described by the origin xy and the target resolution.
9734      */
9735     getZoomTargetCenter: function (xy, resolution) {
9736         var lonlat = null,
9737             size = this.getSize(),
9738             deltaX  = size.w/2 - xy.x,
9739             deltaY  = xy.y - size.h/2,
9740             zoomPoint = this.getLonLatFromPixel(xy);
9741         if (zoomPoint) {
9742             lonlat = new OpenLayers.LonLat(
9743                 zoomPoint.lon + deltaX * resolution,
9744                 zoomPoint.lat + deltaY * resolution
9745             );
9746         }
9747         return lonlat;
9748     },
9749         
9750   //
9751   // CONVENIENCE TRANSLATION FUNCTIONS FOR API
9752   //
9753
9754     /**
9755      * APIMethod: getLonLatFromPixel
9756      * 
9757      * Parameters:
9758      * px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object with
9759      *                                  a 'x' and 'y' properties.
9760      *
9761      * Returns:
9762      * {<OpenLayers.LonLat>} An OpenLayers.LonLat corresponding to the given
9763      *                       OpenLayers.Pixel, translated into lon/lat by the 
9764      *                       current base layer
9765      */
9766     getLonLatFromPixel: function (px) {
9767         return this.getLonLatFromViewPortPx(px);
9768     },
9769
9770     /**
9771      * APIMethod: getPixelFromLonLat
9772      * Returns a pixel location given a map location.  The map location is
9773      *     translated to an integer pixel location (in viewport pixel
9774      *     coordinates) by the current base layer.
9775      * 
9776      * Parameters:
9777      * lonlat - {<OpenLayers.LonLat>} A map location.
9778      * 
9779      * Returns: 
9780      * {<OpenLayers.Pixel>} An OpenLayers.Pixel corresponding to the 
9781      *     <OpenLayers.LonLat> translated into view port pixels by the current
9782      *     base layer.
9783      */
9784     getPixelFromLonLat: function (lonlat) {
9785         var px = this.getViewPortPxFromLonLat(lonlat);
9786         px.x = Math.round(px.x);
9787         px.y = Math.round(px.y);
9788         return px;
9789     },
9790     
9791     /**
9792      * Method: getGeodesicPixelSize
9793      * 
9794      * Parameters:
9795      * px - {<OpenLayers.Pixel>} The pixel to get the geodesic length for. If
9796      *     not provided, the center pixel of the map viewport will be used.
9797      * 
9798      * Returns:
9799      * {<OpenLayers.Size>} The geodesic size of the pixel in kilometers.
9800      */
9801     getGeodesicPixelSize: function(px) {
9802         var lonlat = px ? this.getLonLatFromPixel(px) : (
9803             this.getCachedCenter() || new OpenLayers.LonLat(0, 0));
9804         var res = this.getResolution();
9805         var left = lonlat.add(-res / 2, 0);
9806         var right = lonlat.add(res / 2, 0);
9807         var bottom = lonlat.add(0, -res / 2);
9808         var top = lonlat.add(0, res / 2);
9809         var dest = new OpenLayers.Projection("EPSG:4326");
9810         var source = this.getProjectionObject() || dest;
9811         if(!source.equals(dest)) {
9812             left.transform(source, dest);
9813             right.transform(source, dest);
9814             bottom.transform(source, dest);
9815             top.transform(source, dest);
9816         }
9817         
9818         return new OpenLayers.Size(
9819             OpenLayers.Util.distVincenty(left, right),
9820             OpenLayers.Util.distVincenty(bottom, top)
9821         );
9822     },
9823
9824
9825
9826   //
9827   // TRANSLATION: ViewPortPx <-> LayerPx
9828   //
9829
9830     /**
9831      * APIMethod: getViewPortPxFromLayerPx
9832      * 
9833      * Parameters:
9834      * layerPx - {<OpenLayers.Pixel>}
9835      * 
9836      * Returns:
9837      * {<OpenLayers.Pixel>} Layer Pixel translated into ViewPort Pixel 
9838      *                      coordinates
9839      */
9840     getViewPortPxFromLayerPx:function(layerPx) {
9841         var viewPortPx = null;
9842         if (layerPx != null) {
9843             var dX = this.layerContainerOriginPx.x;
9844             var dY = this.layerContainerOriginPx.y;
9845             viewPortPx = layerPx.add(dX, dY);            
9846         }
9847         return viewPortPx;
9848     },
9849     
9850     /**
9851      * APIMethod: getLayerPxFromViewPortPx
9852      * 
9853      * Parameters:
9854      * viewPortPx - {<OpenLayers.Pixel>}
9855      * 
9856      * Returns:
9857      * {<OpenLayers.Pixel>} ViewPort Pixel translated into Layer Pixel 
9858      *                      coordinates
9859      */
9860     getLayerPxFromViewPortPx:function(viewPortPx) {
9861         var layerPx = null;
9862         if (viewPortPx != null) {
9863             var dX = -this.layerContainerOriginPx.x;
9864             var dY = -this.layerContainerOriginPx.y;
9865             layerPx = viewPortPx.add(dX, dY);
9866             if (isNaN(layerPx.x) || isNaN(layerPx.y)) {
9867                 layerPx = null;
9868             }
9869         }
9870         return layerPx;
9871     },
9872     
9873   //
9874   // TRANSLATION: LonLat <-> LayerPx
9875   //
9876
9877     /**
9878      * Method: getLonLatFromLayerPx
9879      * 
9880      * Parameters:
9881      * px - {<OpenLayers.Pixel>}
9882      *
9883      * Returns:
9884      * {<OpenLayers.LonLat>}
9885      */
9886     getLonLatFromLayerPx: function (px) {
9887        //adjust for displacement of layerContainerDiv
9888        px = this.getViewPortPxFromLayerPx(px);
9889        return this.getLonLatFromViewPortPx(px);         
9890     },
9891     
9892     /**
9893      * APIMethod: getLayerPxFromLonLat
9894      * 
9895      * Parameters:
9896      * lonlat - {<OpenLayers.LonLat>} lonlat
9897      *
9898      * Returns:
9899      * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in 
9900      *                      <OpenLayers.LonLat>, translated into layer pixels 
9901      *                      by the current base layer
9902      */
9903     getLayerPxFromLonLat: function (lonlat) {
9904        //adjust for displacement of layerContainerDiv
9905        var px = this.getPixelFromLonLat(lonlat);
9906        return this.getLayerPxFromViewPortPx(px);         
9907     },
9908
9909     /**
9910      * Method: applyTransform
9911      * Applies the given transform to the <layerContainerDiv>. This method has
9912      * a 2-stage fallback from translate3d/scale3d via translate/scale to plain
9913      * style.left/style.top, in which case no scaling is supported.
9914      *
9915      * Parameters:
9916      * x - {Number} x parameter for the translation. Defaults to the x value of
9917      *     the map's <layerContainerOriginPx>
9918      * y - {Number} y parameter for the translation. Defaults to the y value of
9919      *     the map's <layerContainerOriginPx>
9920      * scale - {Number} scale. Defaults to 1 if not provided.
9921      */
9922      applyTransform: function(x, y, scale) {
9923          scale = scale || 1;
9924          var origin = this.layerContainerOriginPx,
9925              needTransform = scale !== 1;
9926          x = x || origin.x;
9927          y = y || origin.y;
9928             
9929          var style = this.layerContainerDiv.style,
9930              transform = this.applyTransform.transform,
9931              template = this.applyTransform.template;
9932         
9933          if (transform === undefined) {
9934              transform = OpenLayers.Util.vendorPrefix.style('transform');
9935              this.applyTransform.transform = transform;
9936              if (transform) {
9937                  // Try translate3d, but only if the viewPortDiv has a transform
9938                  // defined in a stylesheet
9939                  var computedStyle = OpenLayers.Element.getStyle(this.viewPortDiv,
9940                      OpenLayers.Util.vendorPrefix.css('transform'));
9941                  if (!computedStyle || computedStyle !== 'none') {
9942                      template = ['translate3d(', ',0) ', 'scale3d(', ',1)'];
9943                      style[transform] = [template[0], '0,0', template[1]].join('');
9944                  }
9945                  // If no transform is defined in the stylesheet or translate3d
9946                  // does not stick, use translate and scale
9947                  if (!template || !~style[transform].indexOf(template[0])) {
9948                      template = ['translate(', ') ', 'scale(', ')'];
9949                  }
9950                  this.applyTransform.template = template;
9951              }
9952          }
9953          
9954          // If we do 3d transforms, we always want to use them. If we do 2d
9955          // transforms, we only use them when we need to.
9956          if (transform !== null && (template[0] === 'translate3d(' || needTransform === true)) {
9957              // Our 2d transforms are combined with style.left and style.top, so
9958              // adjust x and y values and set the origin as left and top
9959              if (needTransform === true && template[0] === 'translate(') {
9960                  x -= origin.x;
9961                  y -= origin.y;
9962                  style.left = origin.x + 'px';
9963                  style.top = origin.y + 'px';
9964              }
9965              style[transform] = [
9966                  template[0], x, 'px,', y, 'px', template[1],
9967                  template[2], scale, ',', scale, template[3]
9968              ].join('');
9969          } else {
9970              style.left = x + 'px';
9971              style.top = y + 'px';
9972              // We previously might have had needTransform, so remove transform
9973              if (transform !== null) {
9974                  style[transform] = '';
9975              }
9976          }
9977      },
9978     
9979     CLASS_NAME: "OpenLayers.Map"
9980 });
9981
9982 /**
9983  * Constant: TILE_WIDTH
9984  * {Integer} 256 Default tile width (unless otherwise specified)
9985  */
9986 OpenLayers.Map.TILE_WIDTH = 256;
9987 /**
9988  * Constant: TILE_HEIGHT
9989  * {Integer} 256 Default tile height (unless otherwise specified)
9990  */
9991 OpenLayers.Map.TILE_HEIGHT = 256;
9992 /* ======================================================================
9993     OpenLayers/Feature.js
9994    ====================================================================== */
9995
9996 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
9997  * full list of contributors). Published under the 2-clause BSD license.
9998  * See license.txt in the OpenLayers distribution or repository for the
9999  * full text of the license. */
10000
10001
10002 /**
10003  * @requires OpenLayers/BaseTypes/Class.js
10004  * @requires OpenLayers/Util.js
10005  */
10006
10007 /**
10008  * Class: OpenLayers.Feature
10009  * Features are combinations of geography and attributes. The OpenLayers.Feature
10010  *     class specifically combines a marker and a lonlat.
10011  */
10012 OpenLayers.Feature = OpenLayers.Class({
10013
10014     /** 
10015      * Property: layer 
10016      * {<OpenLayers.Layer>} 
10017      */
10018     layer: null,
10019
10020     /** 
10021      * Property: id 
10022      * {String} 
10023      */
10024     id: null,
10025     
10026     /** 
10027      * Property: lonlat 
10028      * {<OpenLayers.LonLat>} 
10029      */
10030     lonlat: null,
10031
10032     /** 
10033      * Property: data 
10034      * {Object} 
10035      */
10036     data: null,
10037
10038     /** 
10039      * Property: marker 
10040      * {<OpenLayers.Marker>} 
10041      */
10042     marker: null,
10043
10044     /**
10045      * APIProperty: popupClass
10046      * {<OpenLayers.Class>} The class which will be used to instantiate
10047      *     a new Popup. Default is <OpenLayers.Popup.Anchored>.
10048      */
10049     popupClass: null,
10050
10051     /** 
10052      * Property: popup 
10053      * {<OpenLayers.Popup>} 
10054      */
10055     popup: null,
10056
10057     /** 
10058      * Constructor: OpenLayers.Feature
10059      * Constructor for features.
10060      *
10061      * Parameters:
10062      * layer - {<OpenLayers.Layer>} 
10063      * lonlat - {<OpenLayers.LonLat>} 
10064      * data - {Object} 
10065      * 
10066      * Returns:
10067      * {<OpenLayers.Feature>}
10068      */
10069     initialize: function(layer, lonlat, data) {
10070         this.layer = layer;
10071         this.lonlat = lonlat;
10072         this.data = (data != null) ? data : {};
10073         this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_"); 
10074     },
10075
10076     /** 
10077      * Method: destroy
10078      * nullify references to prevent circular references and memory leaks
10079      */
10080     destroy: function() {
10081
10082         //remove the popup from the map
10083         if ((this.layer != null) && (this.layer.map != null)) {
10084             if (this.popup != null) {
10085                 this.layer.map.removePopup(this.popup);
10086             }
10087         }
10088         // remove the marker from the layer
10089         if (this.layer != null && this.marker != null) {
10090             this.layer.removeMarker(this.marker);
10091         }
10092
10093         this.layer = null;
10094         this.id = null;
10095         this.lonlat = null;
10096         this.data = null;
10097         if (this.marker != null) {
10098             this.destroyMarker(this.marker);
10099             this.marker = null;
10100         }
10101         if (this.popup != null) {
10102             this.destroyPopup(this.popup);
10103             this.popup = null;
10104         }
10105     },
10106     
10107     /**
10108      * Method: onScreen
10109      * 
10110      * Returns:
10111      * {Boolean} Whether or not the feature is currently visible on screen
10112      *           (based on its 'lonlat' property)
10113      */
10114     onScreen:function() {
10115         
10116         var onScreen = false;
10117         if ((this.layer != null) && (this.layer.map != null)) {
10118             var screenBounds = this.layer.map.getExtent();
10119             onScreen = screenBounds.containsLonLat(this.lonlat);
10120         }    
10121         return onScreen;
10122     },
10123     
10124
10125     /**
10126      * Method: createMarker
10127      * Based on the data associated with the Feature, create and return a marker object.
10128      *
10129      * Returns: 
10130      * {<OpenLayers.Marker>} A Marker Object created from the 'lonlat' and 'icon' properties
10131      *          set in this.data. If no 'lonlat' is set, returns null. If no
10132      *          'icon' is set, OpenLayers.Marker() will load the default image.
10133      *          
10134      *          Note - this.marker is set to return value
10135      * 
10136      */
10137     createMarker: function() {
10138
10139         if (this.lonlat != null) {
10140             this.marker = new OpenLayers.Marker(this.lonlat, this.data.icon);
10141         }
10142         return this.marker;
10143     },
10144
10145     /**
10146      * Method: destroyMarker
10147      * Destroys marker.
10148      * If user overrides the createMarker() function, s/he should be able
10149      *   to also specify an alternative function for destroying it
10150      */
10151     destroyMarker: function() {
10152         this.marker.destroy();  
10153     },
10154
10155     /**
10156      * Method: createPopup
10157      * Creates a popup object created from the 'lonlat', 'popupSize',
10158      *     and 'popupContentHTML' properties set in this.data. It uses
10159      *     this.marker.icon as default anchor. 
10160      *  
10161      *  If no 'lonlat' is set, returns null. 
10162      *  If no this.marker has been created, no anchor is sent.
10163      *
10164      *  Note - the returned popup object is 'owned' by the feature, so you
10165      *      cannot use the popup's destroy method to discard the popup.
10166      *      Instead, you must use the feature's destroyPopup
10167      * 
10168      *  Note - this.popup is set to return value
10169      * 
10170      * Parameters: 
10171      * closeBox - {Boolean} create popup with closebox or not
10172      * 
10173      * Returns:
10174      * {<OpenLayers.Popup>} Returns the created popup, which is also set
10175      *     as 'popup' property of this feature. Will be of whatever type
10176      *     specified by this feature's 'popupClass' property, but must be
10177      *     of type <OpenLayers.Popup>.
10178      * 
10179      */
10180     createPopup: function(closeBox) {
10181
10182         if (this.lonlat != null) {
10183             if (!this.popup) {
10184                 var anchor = (this.marker) ? this.marker.icon : null;
10185                 var popupClass = this.popupClass ? 
10186                     this.popupClass : OpenLayers.Popup.Anchored;
10187                 this.popup = new popupClass(this.id + "_popup", 
10188                                             this.lonlat,
10189                                             this.data.popupSize,
10190                                             this.data.popupContentHTML,
10191                                             anchor, 
10192                                             closeBox); 
10193             }    
10194             if (this.data.overflow != null) {
10195                 this.popup.contentDiv.style.overflow = this.data.overflow;
10196             }    
10197             
10198             this.popup.feature = this;
10199         }        
10200         return this.popup;
10201     },
10202
10203     
10204     /**
10205      * Method: destroyPopup
10206      * Destroys the popup created via createPopup.
10207      *
10208      * As with the marker, if user overrides the createPopup() function, s/he 
10209      *   should also be able to override the destruction
10210      */
10211     destroyPopup: function() {
10212         if (this.popup) {
10213             this.popup.feature = null;
10214             this.popup.destroy();
10215             this.popup = null;
10216         }    
10217     },
10218
10219     CLASS_NAME: "OpenLayers.Feature"
10220 });
10221 /* ======================================================================
10222     OpenLayers/Feature/Vector.js
10223    ====================================================================== */
10224
10225 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
10226  * full list of contributors). Published under the 2-clause BSD license.
10227  * See license.txt in the OpenLayers distribution or repository for the
10228  * full text of the license. */
10229
10230 // TRASH THIS
10231 OpenLayers.State = {
10232     /** states */
10233     UNKNOWN: 'Unknown',
10234     INSERT: 'Insert',
10235     UPDATE: 'Update',
10236     DELETE: 'Delete'
10237 };
10238
10239 /**
10240  * @requires OpenLayers/Feature.js
10241  * @requires OpenLayers/Util.js
10242  */
10243
10244 /**
10245  * Class: OpenLayers.Feature.Vector
10246  * Vector features use the OpenLayers.Geometry classes as geometry description.
10247  * They have an 'attributes' property, which is the data object, and a 'style'
10248  * property, the default values of which are defined in the 
10249  * <OpenLayers.Feature.Vector.style> objects.
10250  * 
10251  * Inherits from:
10252  *  - <OpenLayers.Feature>
10253  */
10254 OpenLayers.Feature.Vector = OpenLayers.Class(OpenLayers.Feature, {
10255
10256     /** 
10257      * Property: fid 
10258      * {String} 
10259      */
10260     fid: null,
10261     
10262     /** 
10263      * APIProperty: geometry 
10264      * {<OpenLayers.Geometry>} 
10265      */
10266     geometry: null,
10267
10268     /** 
10269      * APIProperty: attributes 
10270      * {Object} This object holds arbitrary, serializable properties that
10271      *     describe the feature.
10272      */
10273     attributes: null,
10274
10275     /**
10276      * Property: bounds
10277      * {<OpenLayers.Bounds>} The box bounding that feature's geometry, that
10278      *     property can be set by an <OpenLayers.Format> object when
10279      *     deserializing the feature, so in most cases it represents an
10280      *     information set by the server. 
10281      */
10282     bounds: null,
10283
10284     /** 
10285      * Property: state 
10286      * {String} 
10287      */
10288     state: null,
10289     
10290     /** 
10291      * APIProperty: style 
10292      * {Object} 
10293      */
10294     style: null,
10295
10296     /**
10297      * APIProperty: url
10298      * {String} If this property is set it will be taken into account by
10299      *     {<OpenLayers.HTTP>} when updating or deleting the feature.
10300      */
10301     url: null,
10302     
10303     /**
10304      * Property: renderIntent
10305      * {String} rendering intent currently being used
10306      */
10307     renderIntent: "default",
10308     
10309     /**
10310      * APIProperty: modified
10311      * {Object} An object with the originals of the geometry and attributes of
10312      * the feature, if they were changed. Currently this property is only read
10313      * by <OpenLayers.Format.WFST.v1>, and written by
10314      * <OpenLayers.Control.ModifyFeature>, which sets the geometry property.
10315      * Applications can set the originals of modified attributes in the
10316      * attributes property. Note that applications have to check if this
10317      * object and the attributes property is already created before using it.
10318      * After a change made with ModifyFeature, this object could look like
10319      *
10320      * (code)
10321      * {
10322      *     geometry: >Object
10323      * }
10324      * (end)
10325      *
10326      * When an application has made changes to feature attributes, it could
10327      * have set the attributes to something like this:
10328      *
10329      * (code)
10330      * {
10331      *     attributes: {
10332      *         myAttribute: "original"
10333      *     }
10334      * }
10335      * (end)
10336      *
10337      * Note that <OpenLayers.Format.WFST.v1> only checks for truthy values in
10338      * *modified.geometry* and the attribute names in *modified.attributes*,
10339      * but it is recommended to set the original values (and not just true) as
10340      * attribute value, so applications could use this information to undo
10341      * changes.
10342      */
10343     modified: null,
10344
10345     /** 
10346      * Constructor: OpenLayers.Feature.Vector
10347      * Create a vector feature. 
10348      * 
10349      * Parameters:
10350      * geometry - {<OpenLayers.Geometry>} The geometry that this feature
10351      *     represents.
10352      * attributes - {Object} An optional object that will be mapped to the
10353      *     <attributes> property. 
10354      * style - {Object} An optional style object.
10355      */
10356     initialize: function(geometry, attributes, style) {
10357         OpenLayers.Feature.prototype.initialize.apply(this,
10358                                                       [null, null, attributes]);
10359         this.lonlat = null;
10360         this.geometry = geometry ? geometry : null;
10361         this.state = null;
10362         this.attributes = {};
10363         if (attributes) {
10364             this.attributes = OpenLayers.Util.extend(this.attributes,
10365                                                      attributes);
10366         }
10367         this.style = style ? style : null; 
10368     },
10369     
10370     /** 
10371      * Method: destroy
10372      * nullify references to prevent circular references and memory leaks
10373      */
10374     destroy: function() {
10375         if (this.layer) {
10376             this.layer.removeFeatures(this);
10377             this.layer = null;
10378         }
10379             
10380         this.geometry = null;
10381         this.modified = null;
10382         OpenLayers.Feature.prototype.destroy.apply(this, arguments);
10383     },
10384     
10385     /**
10386      * Method: clone
10387      * Create a clone of this vector feature.  Does not set any non-standard
10388      *     properties.
10389      *
10390      * Returns:
10391      * {<OpenLayers.Feature.Vector>} An exact clone of this vector feature.
10392      */
10393     clone: function () {
10394         return new OpenLayers.Feature.Vector(
10395             this.geometry ? this.geometry.clone() : null,
10396             this.attributes,
10397             this.style);
10398     },
10399
10400     /**
10401      * Method: onScreen
10402      * Determine whether the feature is within the map viewport.  This method
10403      *     tests for an intersection between the geometry and the viewport
10404      *     bounds.  If a more efficient but less precise geometry bounds
10405      *     intersection is desired, call the method with the boundsOnly
10406      *     parameter true.
10407      *
10408      * Parameters:
10409      * boundsOnly - {Boolean} Only test whether a feature's bounds intersects
10410      *     the viewport bounds.  Default is false.  If false, the feature's
10411      *     geometry must intersect the viewport for onScreen to return true.
10412      * 
10413      * Returns:
10414      * {Boolean} The feature is currently visible on screen (optionally
10415      *     based on its bounds if boundsOnly is true).
10416      */
10417     onScreen:function(boundsOnly) {
10418         var onScreen = false;
10419         if(this.layer && this.layer.map) {
10420             var screenBounds = this.layer.map.getExtent();
10421             if(boundsOnly) {
10422                 var featureBounds = this.geometry.getBounds();
10423                 onScreen = screenBounds.intersectsBounds(featureBounds);
10424             } else {
10425                 var screenPoly = screenBounds.toGeometry();
10426                 onScreen = screenPoly.intersects(this.geometry);
10427             }
10428         }    
10429         return onScreen;
10430     },
10431
10432     /**
10433      * Method: getVisibility
10434      * Determine whether the feature is displayed or not. It may not displayed
10435      *     because:
10436      *     - its style display property is set to 'none',
10437      *     - it doesn't belong to any layer,
10438      *     - the styleMap creates a symbolizer with display property set to 'none'
10439      *          for it,
10440      *     - the layer which it belongs to is not visible.
10441      * 
10442      * Returns:
10443      * {Boolean} The feature is currently displayed.
10444      */
10445     getVisibility: function() {
10446         return !(this.style && this.style.display == 'none' ||
10447                  !this.layer ||
10448                  this.layer && this.layer.styleMap &&
10449                  this.layer.styleMap.createSymbolizer(this, this.renderIntent).display == 'none' ||
10450                  this.layer && !this.layer.getVisibility());
10451     },
10452     
10453     /**
10454      * Method: createMarker
10455      * HACK - we need to decide if all vector features should be able to
10456      *     create markers
10457      * 
10458      * Returns:
10459      * {<OpenLayers.Marker>} For now just returns null
10460      */
10461     createMarker: function() {
10462         return null;
10463     },
10464
10465     /**
10466      * Method: destroyMarker
10467      * HACK - we need to decide if all vector features should be able to
10468      *     delete markers
10469      * 
10470      * If user overrides the createMarker() function, s/he should be able
10471      *   to also specify an alternative function for destroying it
10472      */
10473     destroyMarker: function() {
10474         // pass
10475     },
10476
10477     /**
10478      * Method: createPopup
10479      * HACK - we need to decide if all vector features should be able to
10480      *     create popups
10481      * 
10482      * Returns:
10483      * {<OpenLayers.Popup>} For now just returns null
10484      */
10485     createPopup: function() {
10486         return null;
10487     },
10488
10489     /**
10490      * Method: atPoint
10491      * Determins whether the feature intersects with the specified location.
10492      * 
10493      * Parameters: 
10494      * lonlat - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
10495      *     object with a 'lon' and 'lat' properties.
10496      * toleranceLon - {float} Optional tolerance in Geometric Coords
10497      * toleranceLat - {float} Optional tolerance in Geographic Coords
10498      * 
10499      * Returns:
10500      * {Boolean} Whether or not the feature is at the specified location
10501      */
10502     atPoint: function(lonlat, toleranceLon, toleranceLat) {
10503         var atPoint = false;
10504         if(this.geometry) {
10505             atPoint = this.geometry.atPoint(lonlat, toleranceLon, 
10506                                                     toleranceLat);
10507         }
10508         return atPoint;
10509     },
10510
10511     /**
10512      * Method: destroyPopup
10513      * HACK - we need to decide if all vector features should be able to
10514      * delete popups
10515      */
10516     destroyPopup: function() {
10517         // pass
10518     },
10519
10520     /**
10521      * Method: move
10522      * Moves the feature and redraws it at its new location
10523      *
10524      * Parameters:
10525      * location - {<OpenLayers.LonLat> or <OpenLayers.Pixel>} the
10526      *         location to which to move the feature.
10527      */
10528     move: function(location) {
10529
10530         if(!this.layer || !this.geometry.move){
10531             //do nothing if no layer or immoveable geometry
10532             return undefined;
10533         }
10534
10535         var pixel;
10536         if (location.CLASS_NAME == "OpenLayers.LonLat") {
10537             pixel = this.layer.getViewPortPxFromLonLat(location);
10538         } else {
10539             pixel = location;
10540         }
10541         
10542         var lastPixel = this.layer.getViewPortPxFromLonLat(this.geometry.getBounds().getCenterLonLat());
10543         var res = this.layer.map.getResolution();
10544         this.geometry.move(res * (pixel.x - lastPixel.x),
10545                            res * (lastPixel.y - pixel.y));
10546         this.layer.drawFeature(this);
10547         return lastPixel;
10548     },
10549     
10550     /**
10551      * Method: toState
10552      * Sets the new state
10553      *
10554      * Parameters:
10555      * state - {String} 
10556      */
10557     toState: function(state) {
10558         if (state == OpenLayers.State.UPDATE) {
10559             switch (this.state) {
10560                 case OpenLayers.State.UNKNOWN:
10561                 case OpenLayers.State.DELETE:
10562                     this.state = state;
10563                     break;
10564                 case OpenLayers.State.UPDATE:
10565                 case OpenLayers.State.INSERT:
10566                     break;
10567             }
10568         } else if (state == OpenLayers.State.INSERT) {
10569             switch (this.state) {
10570                 case OpenLayers.State.UNKNOWN:
10571                     break;
10572                 default:
10573                     this.state = state;
10574                     break;
10575             }
10576         } else if (state == OpenLayers.State.DELETE) {
10577             switch (this.state) {
10578                 case OpenLayers.State.INSERT:
10579                     // the feature should be destroyed
10580                     break;
10581                 case OpenLayers.State.DELETE:
10582                     break;
10583                 case OpenLayers.State.UNKNOWN:
10584                 case OpenLayers.State.UPDATE:
10585                     this.state = state;
10586                     break;
10587             }
10588         } else if (state == OpenLayers.State.UNKNOWN) {
10589             this.state = state;
10590         }
10591     },
10592     
10593     CLASS_NAME: "OpenLayers.Feature.Vector"
10594 });
10595
10596
10597 /**
10598  * Constant: OpenLayers.Feature.Vector.style
10599  * OpenLayers features can have a number of style attributes. The 'default' 
10600  *     style will typically be used if no other style is specified. These
10601  *     styles correspond for the most part, to the styling properties defined
10602  *     by the SVG standard. 
10603  *     Information on fill properties: http://www.w3.org/TR/SVG/painting.html#FillProperties
10604  *     Information on stroke properties: http://www.w3.org/TR/SVG/painting.html#StrokeProperties
10605  *
10606  * Symbolizer properties:
10607  * fill - {Boolean} Set to false if no fill is desired.
10608  * fillColor - {String} Hex fill color.  Default is "#ee9900".
10609  * fillOpacity - {Number} Fill opacity (0-1).  Default is 0.4 
10610  * stroke - {Boolean} Set to false if no stroke is desired.
10611  * strokeColor - {String} Hex stroke color.  Default is "#ee9900".
10612  * strokeOpacity - {Number} Stroke opacity (0-1).  Default is 1.
10613  * strokeWidth - {Number} Pixel stroke width.  Default is 1.
10614  * strokeLinecap - {String} Stroke cap type.  Default is "round".  [butt | round | square]
10615  * strokeDashstyle - {String} Stroke dash style.  Default is "solid". [dot | dash | dashdot | longdash | longdashdot | solid]
10616  * graphic - {Boolean} Set to false if no graphic is desired.
10617  * pointRadius - {Number} Pixel point radius.  Default is 6.
10618  * pointerEvents - {String}  Default is "visiblePainted".
10619  * cursor - {String} Default is "".
10620  * externalGraphic - {String} Url to an external graphic that will be used for rendering points.
10621  * graphicWidth - {Number} Pixel width for sizing an external graphic.
10622  * graphicHeight - {Number} Pixel height for sizing an external graphic.
10623  * graphicOpacity - {Number} Opacity (0-1) for an external graphic.
10624  * graphicXOffset - {Number} Pixel offset along the positive x axis for displacing an external graphic.
10625  * graphicYOffset - {Number} Pixel offset along the positive y axis for displacing an external graphic.
10626  * 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).
10627  * graphicZIndex - {Number} The integer z-index value to use in rendering.
10628  * graphicName - {String} Named graphic to use when rendering points.  Supported values include "circle" (default),
10629  *     "square", "star", "x", "cross", "triangle".
10630  * graphicTitle - {String} Tooltip when hovering over a feature. *deprecated*, use title instead
10631  * title - {String} Tooltip when hovering over a feature. Not supported by the canvas renderer.
10632  * backgroundGraphic - {String} Url to a graphic to be used as the background under an externalGraphic.
10633  * backgroundGraphicZIndex - {Number} The integer z-index value to use in rendering the background graphic.
10634  * backgroundXOffset - {Number} The x offset (in pixels) for the background graphic.
10635  * backgroundYOffset - {Number} The y offset (in pixels) for the background graphic.
10636  * backgroundHeight - {Number} The height of the background graphic.  If not provided, the graphicHeight will be used.
10637  * backgroundWidth - {Number} The width of the background width.  If not provided, the graphicWidth will be used.
10638  * label - {String} The text for an optional label. For browsers that use the canvas renderer, this requires either
10639  *     fillText or mozDrawText to be available.
10640  * labelAlign - {String} Label alignment. This specifies the insertion point relative to the text. It is a string
10641  *     composed of two characters. The first character is for the horizontal alignment, the second for the vertical
10642  *     alignment. Valid values for horizontal alignment: "l"=left, "c"=center, "r"=right. Valid values for vertical
10643  *     alignment: "t"=top, "m"=middle, "b"=bottom. Example values: "lt", "cm", "rb". Default is "cm".
10644  * labelXOffset - {Number} Pixel offset along the positive x axis for displacing the label. Not supported by the canvas renderer.
10645  * labelYOffset - {Number} Pixel offset along the positive y axis for displacing the label. Not supported by the canvas renderer.
10646  * labelSelect - {Boolean} If set to true, labels will be selectable using SelectFeature or similar controls.
10647  *     Default is false.
10648  * labelOutlineColor - {String} The color of the label outline. Default is 'white'. Only supported by the canvas & SVG renderers.
10649  * labelOutlineWidth - {Number} The width of the label outline. Default is 3, set to 0 or null to disable. Only supported by the  SVG renderers.
10650  * labelOutlineOpacity - {Number} The opacity (0-1) of the label outline. Default is fontOpacity. Only supported by the canvas & SVG renderers.
10651  * fontColor - {String} The font color for the label, to be provided like CSS.
10652  * fontOpacity - {Number} Opacity (0-1) for the label
10653  * fontFamily - {String} The font family for the label, to be provided like in CSS.
10654  * fontSize - {String} The font size for the label, to be provided like in CSS.
10655  * fontStyle - {String} The font style for the label, to be provided like in CSS.
10656  * fontWeight - {String} The font weight for the label, to be provided like in CSS.
10657  * display - {String} Symbolizers will have no effect if display is set to "none".  All other values have no effect.
10658  */ 
10659 OpenLayers.Feature.Vector.style = {
10660     'default': {
10661         fillColor: "#ee9900",
10662         fillOpacity: 0.4, 
10663         hoverFillColor: "white",
10664         hoverFillOpacity: 0.8,
10665         strokeColor: "#ee9900",
10666         strokeOpacity: 1,
10667         strokeWidth: 1,
10668         strokeLinecap: "round",
10669         strokeDashstyle: "solid",
10670         hoverStrokeColor: "red",
10671         hoverStrokeOpacity: 1,
10672         hoverStrokeWidth: 0.2,
10673         pointRadius: 6,
10674         hoverPointRadius: 1,
10675         hoverPointUnit: "%",
10676         pointerEvents: "visiblePainted",
10677         cursor: "inherit",
10678         fontColor: "#000000",
10679         labelAlign: "cm",
10680         labelOutlineColor: "white",
10681         labelOutlineWidth: 3
10682     },
10683     'select': {
10684         fillColor: "blue",
10685         fillOpacity: 0.4, 
10686         hoverFillColor: "white",
10687         hoverFillOpacity: 0.8,
10688         strokeColor: "blue",
10689         strokeOpacity: 1,
10690         strokeWidth: 2,
10691         strokeLinecap: "round",
10692         strokeDashstyle: "solid",
10693         hoverStrokeColor: "red",
10694         hoverStrokeOpacity: 1,
10695         hoverStrokeWidth: 0.2,
10696         pointRadius: 6,
10697         hoverPointRadius: 1,
10698         hoverPointUnit: "%",
10699         pointerEvents: "visiblePainted",
10700         cursor: "pointer",
10701         fontColor: "#000000",
10702         labelAlign: "cm",
10703         labelOutlineColor: "white",
10704         labelOutlineWidth: 3
10705
10706     },
10707     'temporary': {
10708         fillColor: "#66cccc",
10709         fillOpacity: 0.2, 
10710         hoverFillColor: "white",
10711         hoverFillOpacity: 0.8,
10712         strokeColor: "#66cccc",
10713         strokeOpacity: 1,
10714         strokeLinecap: "round",
10715         strokeWidth: 2,
10716         strokeDashstyle: "solid",
10717         hoverStrokeColor: "red",
10718         hoverStrokeOpacity: 1,
10719         hoverStrokeWidth: 0.2,
10720         pointRadius: 6,
10721         hoverPointRadius: 1,
10722         hoverPointUnit: "%",
10723         pointerEvents: "visiblePainted",
10724         cursor: "inherit",
10725         fontColor: "#000000",
10726         labelAlign: "cm",
10727         labelOutlineColor: "white",
10728         labelOutlineWidth: 3
10729
10730     },
10731     'delete': {
10732         display: "none"
10733     }
10734 };    
10735 /* ======================================================================
10736     OpenLayers/Style.js
10737    ====================================================================== */
10738
10739 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
10740  * full list of contributors). Published under the 2-clause BSD license.
10741  * See license.txt in the OpenLayers distribution or repository for the
10742  * full text of the license. */
10743
10744
10745 /**
10746  * @requires OpenLayers/BaseTypes/Class.js
10747  * @requires OpenLayers/Util.js
10748  * @requires OpenLayers/Feature/Vector.js
10749  */
10750
10751 /**
10752  * Class: OpenLayers.Style
10753  * This class represents a UserStyle obtained
10754  *     from a SLD, containing styling rules.
10755  */
10756 OpenLayers.Style = OpenLayers.Class({
10757
10758     /**
10759      * Property: id
10760      * {String} A unique id for this session.
10761      */
10762     id: null,
10763     
10764     /**
10765      * APIProperty: name
10766      * {String}
10767      */
10768     name: null,
10769     
10770     /**
10771      * Property: title
10772      * {String} Title of this style (set if included in SLD)
10773      */
10774     title: null,
10775     
10776     /**
10777      * Property: description
10778      * {String} Description of this style (set if abstract is included in SLD)
10779      */
10780     description: null,
10781
10782     /**
10783      * APIProperty: layerName
10784      * {<String>} name of the layer that this style belongs to, usually
10785      * according to the NamedLayer attribute of an SLD document.
10786      */
10787     layerName: null,
10788     
10789     /**
10790      * APIProperty: isDefault
10791      * {Boolean}
10792      */
10793     isDefault: false,
10794      
10795     /** 
10796      * Property: rules 
10797      * {Array(<OpenLayers.Rule>)}
10798      */
10799     rules: null,
10800     
10801     /**
10802      * APIProperty: context
10803      * {Object} An optional object with properties that symbolizers' property
10804      * values should be evaluated against. If no context is specified,
10805      * feature.attributes will be used
10806      */
10807     context: null,
10808
10809     /**
10810      * Property: defaultStyle
10811      * {Object} hash of style properties to use as default for merging
10812      * rule-based style symbolizers onto. If no rules are defined,
10813      * createSymbolizer will return this style. If <defaultsPerSymbolizer> is set to
10814      * true, the defaultStyle will only be taken into account if there are
10815      * rules defined.
10816      */
10817     defaultStyle: null,
10818     
10819     /**
10820      * Property: defaultsPerSymbolizer
10821      * {Boolean} If set to true, the <defaultStyle> will extend the symbolizer
10822      * of every rule. Properties of the <defaultStyle> will also be used to set
10823      * missing symbolizer properties if the symbolizer has stroke, fill or
10824      * graphic set to true. Default is false.
10825      */
10826     defaultsPerSymbolizer: false,
10827     
10828     /**
10829      * Property: propertyStyles
10830      * {Hash of Boolean} cache of style properties that need to be parsed for
10831      * propertyNames. Property names are keys, values won't be used.
10832      */
10833     propertyStyles: null,
10834     
10835
10836     /** 
10837      * Constructor: OpenLayers.Style
10838      * Creates a UserStyle.
10839      *
10840      * Parameters:
10841      * style        - {Object} Optional hash of style properties that will be
10842      *                used as default style for this style object. This style
10843      *                applies if no rules are specified. Symbolizers defined in
10844      *                rules will extend this default style.
10845      * options - {Object} An optional object with properties to set on the
10846      *     style.
10847      *
10848      * Valid options:
10849      * rules - {Array(<OpenLayers.Rule>)} List of rules to be added to the
10850      *     style.
10851      * 
10852      * Returns:
10853      * {<OpenLayers.Style>}
10854      */
10855     initialize: function(style, options) {
10856
10857         OpenLayers.Util.extend(this, options);
10858         this.rules = [];
10859         if(options && options.rules) {
10860             this.addRules(options.rules);
10861         }
10862
10863         // use the default style from OpenLayers.Feature.Vector if no style
10864         // was given in the constructor
10865         this.setDefaultStyle(style ||
10866                              OpenLayers.Feature.Vector.style["default"]);
10867
10868         this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
10869     },
10870
10871     /** 
10872      * APIMethod: destroy
10873      * nullify references to prevent circular references and memory leaks
10874      */
10875     destroy: function() {
10876         for (var i=0, len=this.rules.length; i<len; i++) {
10877             this.rules[i].destroy();
10878             this.rules[i] = null;
10879         }
10880         this.rules = null;
10881         this.defaultStyle = null;
10882     },
10883     
10884     /**
10885      * Method: createSymbolizer
10886      * creates a style by applying all feature-dependent rules to the base
10887      * style.
10888      * 
10889      * Parameters:
10890      * feature - {<OpenLayers.Feature>} feature to evaluate rules for
10891      * 
10892      * Returns:
10893      * {Object} symbolizer hash
10894      */
10895     createSymbolizer: function(feature) {
10896         var style = this.defaultsPerSymbolizer ? {} : this.createLiterals(
10897             OpenLayers.Util.extend({}, this.defaultStyle), feature);
10898         
10899         var rules = this.rules;
10900
10901         var rule, context;
10902         var elseRules = [];
10903         var appliedRules = false;
10904         for(var i=0, len=rules.length; i<len; i++) {
10905             rule = rules[i];
10906             // does the rule apply?
10907             var applies = rule.evaluate(feature);
10908             
10909             if(applies) {
10910                 if(rule instanceof OpenLayers.Rule && rule.elseFilter) {
10911                     elseRules.push(rule);
10912                 } else {
10913                     appliedRules = true;
10914                     this.applySymbolizer(rule, style, feature);
10915                 }
10916             }
10917         }
10918         
10919         // if no other rules apply, apply the rules with else filters
10920         if(appliedRules == false && elseRules.length > 0) {
10921             appliedRules = true;
10922             for(var i=0, len=elseRules.length; i<len; i++) {
10923                 this.applySymbolizer(elseRules[i], style, feature);
10924             }
10925         }
10926
10927         // don't display if there were rules but none applied
10928         if(rules.length > 0 && appliedRules == false) {
10929             style.display = "none";
10930         }
10931         
10932         if (style.label != null && typeof style.label !== "string") {
10933             style.label = String(style.label);
10934         }
10935         
10936         return style;
10937     },
10938     
10939     /**
10940      * Method: applySymbolizer
10941      *
10942      * Parameters:
10943      * rule - {<OpenLayers.Rule>}
10944      * style - {Object}
10945      * feature - {<OpenLayer.Feature.Vector>}
10946      *
10947      * Returns:
10948      * {Object} A style with new symbolizer applied.
10949      */
10950     applySymbolizer: function(rule, style, feature) {
10951         var symbolizerPrefix = feature.geometry ?
10952                 this.getSymbolizerPrefix(feature.geometry) :
10953                 OpenLayers.Style.SYMBOLIZER_PREFIXES[0];
10954
10955         var symbolizer = rule.symbolizer[symbolizerPrefix] || rule.symbolizer;
10956         
10957         if(this.defaultsPerSymbolizer === true) {
10958             var defaults = this.defaultStyle;
10959             OpenLayers.Util.applyDefaults(symbolizer, {
10960                 pointRadius: defaults.pointRadius
10961             });
10962             if(symbolizer.stroke === true || symbolizer.graphic === true) {
10963                 OpenLayers.Util.applyDefaults(symbolizer, {
10964                     strokeWidth: defaults.strokeWidth,
10965                     strokeColor: defaults.strokeColor,
10966                     strokeOpacity: defaults.strokeOpacity,
10967                     strokeDashstyle: defaults.strokeDashstyle,
10968                     strokeLinecap: defaults.strokeLinecap
10969                 });
10970             }
10971             if(symbolizer.fill === true || symbolizer.graphic === true) {
10972                 OpenLayers.Util.applyDefaults(symbolizer, {
10973                     fillColor: defaults.fillColor,
10974                     fillOpacity: defaults.fillOpacity
10975                 });
10976             }
10977             if(symbolizer.graphic === true) {
10978                 OpenLayers.Util.applyDefaults(symbolizer, {
10979                     pointRadius: this.defaultStyle.pointRadius,
10980                     externalGraphic: this.defaultStyle.externalGraphic,
10981                     graphicName: this.defaultStyle.graphicName,
10982                     graphicOpacity: this.defaultStyle.graphicOpacity,
10983                     graphicWidth: this.defaultStyle.graphicWidth,
10984                     graphicHeight: this.defaultStyle.graphicHeight,
10985                     graphicXOffset: this.defaultStyle.graphicXOffset,
10986                     graphicYOffset: this.defaultStyle.graphicYOffset
10987                 });
10988             }
10989         }
10990
10991         // merge the style with the current style
10992         return this.createLiterals(
10993                 OpenLayers.Util.extend(style, symbolizer), feature);
10994     },
10995     
10996     /**
10997      * Method: createLiterals
10998      * creates literals for all style properties that have an entry in
10999      * <this.propertyStyles>.
11000      * 
11001      * Parameters:
11002      * style   - {Object} style to create literals for. Will be modified
11003      *           inline.
11004      * feature - {Object}
11005      * 
11006      * Returns:
11007      * {Object} the modified style
11008      */
11009     createLiterals: function(style, feature) {
11010         var context = OpenLayers.Util.extend({}, feature.attributes || feature.data);
11011         OpenLayers.Util.extend(context, this.context);
11012         
11013         for (var i in this.propertyStyles) {
11014             style[i] = OpenLayers.Style.createLiteral(style[i], context, feature, i);
11015         }
11016         return style;
11017     },
11018     
11019     /**
11020      * Method: findPropertyStyles
11021      * Looks into all rules for this style and the defaultStyle to collect
11022      * all the style hash property names containing ${...} strings that have
11023      * to be replaced using the createLiteral method before returning them.
11024      * 
11025      * Returns:
11026      * {Object} hash of property names that need createLiteral parsing. The
11027      * name of the property is the key, and the value is true;
11028      */
11029     findPropertyStyles: function() {
11030         var propertyStyles = {};
11031
11032         // check the default style
11033         var style = this.defaultStyle;
11034         this.addPropertyStyles(propertyStyles, style);
11035
11036         // walk through all rules to check for properties in their symbolizer
11037         var rules = this.rules;
11038         var symbolizer, value;
11039         for (var i=0, len=rules.length; i<len; i++) {
11040             symbolizer = rules[i].symbolizer;
11041             for (var key in symbolizer) {
11042                 value = symbolizer[key];
11043                 if (typeof value == "object") {
11044                     // symbolizer key is "Point", "Line" or "Polygon"
11045                     this.addPropertyStyles(propertyStyles, value);
11046                 } else {
11047                     // symbolizer is a hash of style properties
11048                     this.addPropertyStyles(propertyStyles, symbolizer);
11049                     break;
11050                 }
11051             }
11052         }
11053         return propertyStyles;
11054     },
11055     
11056     /**
11057      * Method: addPropertyStyles
11058      * 
11059      * Parameters:
11060      * propertyStyles - {Object} hash to add new property styles to. Will be
11061      *                  modified inline
11062      * symbolizer     - {Object} search this symbolizer for property styles
11063      * 
11064      * Returns:
11065      * {Object} propertyStyles hash
11066      */
11067     addPropertyStyles: function(propertyStyles, symbolizer) {
11068         var property;
11069         for (var key in symbolizer) {
11070             property = symbolizer[key];
11071             if (typeof property == "string" &&
11072                     property.match(/\$\{\w+\}/)) {
11073                 propertyStyles[key] = true;
11074             }
11075         }
11076         return propertyStyles;
11077     },
11078     
11079     /**
11080      * APIMethod: addRules
11081      * Adds rules to this style.
11082      * 
11083      * Parameters:
11084      * rules - {Array(<OpenLayers.Rule>)}
11085      */
11086     addRules: function(rules) {
11087         Array.prototype.push.apply(this.rules, rules);
11088         this.propertyStyles = this.findPropertyStyles();
11089     },
11090     
11091     /**
11092      * APIMethod: setDefaultStyle
11093      * Sets the default style for this style object.
11094      * 
11095      * Parameters:
11096      * style - {Object} Hash of style properties
11097      */
11098     setDefaultStyle: function(style) {
11099         this.defaultStyle = style; 
11100         this.propertyStyles = this.findPropertyStyles();
11101     },
11102         
11103     /**
11104      * Method: getSymbolizerPrefix
11105      * Returns the correct symbolizer prefix according to the
11106      * geometry type of the passed geometry
11107      * 
11108      * Parameters:
11109      * geometry - {<OpenLayers.Geometry>}
11110      * 
11111      * Returns:
11112      * {String} key of the according symbolizer
11113      */
11114     getSymbolizerPrefix: function(geometry) {
11115         var prefixes = OpenLayers.Style.SYMBOLIZER_PREFIXES;
11116         for (var i=0, len=prefixes.length; i<len; i++) {
11117             if (geometry.CLASS_NAME.indexOf(prefixes[i]) != -1) {
11118                 return prefixes[i];
11119             }
11120         }
11121     },
11122     
11123     /**
11124      * APIMethod: clone
11125      * Clones this style.
11126      * 
11127      * Returns:
11128      * {<OpenLayers.Style>} Clone of this style.
11129      */
11130     clone: function() {
11131         var options = OpenLayers.Util.extend({}, this);
11132         // clone rules
11133         if(this.rules) {
11134             options.rules = [];
11135             for(var i=0, len=this.rules.length; i<len; ++i) {
11136                 options.rules.push(this.rules[i].clone());
11137             }
11138         }
11139         // clone context
11140         options.context = this.context && OpenLayers.Util.extend({}, this.context);
11141         //clone default style
11142         var defaultStyle = OpenLayers.Util.extend({}, this.defaultStyle);
11143         return new OpenLayers.Style(defaultStyle, options);
11144     },
11145     
11146     CLASS_NAME: "OpenLayers.Style"
11147 });
11148
11149
11150 /**
11151  * Function: createLiteral
11152  * converts a style value holding a combination of PropertyName and Literal
11153  * into a Literal, taking the property values from the passed features.
11154  * 
11155  * Parameters:
11156  * value - {String} value to parse. If this string contains a construct like
11157  *         "foo ${bar}", then "foo " will be taken as literal, and "${bar}"
11158  *         will be replaced by the value of the "bar" attribute of the passed
11159  *         feature.
11160  * context - {Object} context to take attribute values from
11161  * feature - {<OpenLayers.Feature.Vector>} optional feature to pass to
11162  *           <OpenLayers.String.format> for evaluating functions in the
11163  *           context.
11164  * property - {String} optional, name of the property for which the literal is
11165  *            being created for evaluating functions in the context.
11166  * 
11167  * Returns:
11168  * {String} the parsed value. In the example of the value parameter above, the
11169  * result would be "foo valueOfBar", assuming that the passed feature has an
11170  * attribute named "bar" with the value "valueOfBar".
11171  */
11172 OpenLayers.Style.createLiteral = function(value, context, feature, property) {
11173     if (typeof value == "string" && value.indexOf("${") != -1) {
11174         value = OpenLayers.String.format(value, context, [feature, property]);
11175         value = (isNaN(value) || !value) ? value : parseFloat(value);
11176     }
11177     return value;
11178 };
11179     
11180 /**
11181  * Constant: OpenLayers.Style.SYMBOLIZER_PREFIXES
11182  * {Array} prefixes of the sld symbolizers. These are the
11183  * same as the main geometry types
11184  */
11185 OpenLayers.Style.SYMBOLIZER_PREFIXES = ['Point', 'Line', 'Polygon', 'Text',
11186     'Raster'];
11187 /* ======================================================================
11188     OpenLayers/Rule.js
11189    ====================================================================== */
11190
11191 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
11192  * full list of contributors). Published under the 2-clause BSD license.
11193  * See license.txt in the OpenLayers distribution or repository for the
11194  * full text of the license. */
11195
11196
11197 /**
11198  * @requires OpenLayers/BaseTypes/Class.js
11199  * @requires OpenLayers/Util.js
11200  * @requires OpenLayers/Style.js
11201  */
11202
11203 /**
11204  * Class: OpenLayers.Rule
11205  * This class represents an SLD Rule, as being used for rule-based SLD styling.
11206  */
11207 OpenLayers.Rule = OpenLayers.Class({
11208     
11209     /**
11210      * Property: id
11211      * {String} A unique id for this session.
11212      */
11213     id: null,
11214     
11215     /**
11216      * APIProperty: name
11217      * {String} name of this rule
11218      */
11219     name: null,
11220     
11221     /**
11222      * Property: title
11223      * {String} Title of this rule (set if included in SLD)
11224      */
11225     title: null,
11226     
11227     /**
11228      * Property: description
11229      * {String} Description of this rule (set if abstract is included in SLD)
11230      */
11231     description: null,
11232
11233     /**
11234      * Property: context
11235      * {Object} An optional object with properties that the rule should be
11236      * evaluated against. If no context is specified, feature.attributes will
11237      * be used.
11238      */
11239     context: null,
11240     
11241     /**
11242      * Property: filter
11243      * {<OpenLayers.Filter>} Optional filter for the rule.
11244      */
11245     filter: null,
11246
11247     /**
11248      * Property: elseFilter
11249      * {Boolean} Determines whether this rule is only to be applied only if
11250      * no other rules match (ElseFilter according to the SLD specification). 
11251      * Default is false.  For instances of OpenLayers.Rule, if elseFilter is
11252      * false, the rule will always apply.  For subclasses, the else property is 
11253      * ignored.
11254      */
11255     elseFilter: false,
11256     
11257     /**
11258      * Property: symbolizer
11259      * {Object} Symbolizer or hash of symbolizers for this rule. If hash of
11260      * symbolizers, keys are one or more of ["Point", "Line", "Polygon"]. The
11261      * latter if useful if it is required to style e.g. vertices of a line
11262      * with a point symbolizer. Note, however, that this is not implemented
11263      * yet in OpenLayers, but it is the way how symbolizers are defined in
11264      * SLD.
11265      */
11266     symbolizer: null,
11267     
11268     /**
11269      * Property: symbolizers
11270      * {Array} Collection of symbolizers associated with this rule.  If 
11271      *     provided at construction, the symbolizers array has precedence
11272      *     over the deprecated symbolizer property.  Note that multiple 
11273      *     symbolizers are not currently supported by the vector renderers.
11274      *     Rules with multiple symbolizers are currently only useful for
11275      *     maintaining elements in an SLD document.
11276      */
11277     symbolizers: null,
11278     
11279     /**
11280      * APIProperty: minScaleDenominator
11281      * {Number} or {String} minimum scale at which to draw the feature.
11282      * In the case of a String, this can be a combination of text and
11283      * propertyNames in the form "literal ${propertyName}"
11284      */
11285     minScaleDenominator: null,
11286
11287     /**
11288      * APIProperty: maxScaleDenominator
11289      * {Number} or {String} maximum scale at which to draw the feature.
11290      * In the case of a String, this can be a combination of text and
11291      * propertyNames in the form "literal ${propertyName}"
11292      */
11293     maxScaleDenominator: null,
11294     
11295     /** 
11296      * Constructor: OpenLayers.Rule
11297      * Creates a Rule.
11298      *
11299      * Parameters:
11300      * options - {Object} An optional object with properties to set on the
11301      *           rule
11302      * 
11303      * Returns:
11304      * {<OpenLayers.Rule>}
11305      */
11306     initialize: function(options) {
11307         this.symbolizer = {};
11308         OpenLayers.Util.extend(this, options);
11309         if (this.symbolizers) {
11310             delete this.symbolizer;
11311         }
11312         this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
11313     },
11314
11315     /** 
11316      * APIMethod: destroy
11317      * nullify references to prevent circular references and memory leaks
11318      */
11319     destroy: function() {
11320         for (var i in this.symbolizer) {
11321             this.symbolizer[i] = null;
11322         }
11323         this.symbolizer = null;
11324         delete this.symbolizers;
11325     },
11326     
11327     /**
11328      * APIMethod: evaluate
11329      * evaluates this rule for a specific feature
11330      * 
11331      * Parameters:
11332      * feature - {<OpenLayers.Feature>} feature to apply the rule to.
11333      * 
11334      * Returns:
11335      * {Boolean} true if the rule applies, false if it does not.
11336      * This rule is the default rule and always returns true.
11337      */
11338     evaluate: function(feature) {
11339         var context = this.getContext(feature);
11340         var applies = true;
11341
11342         if (this.minScaleDenominator || this.maxScaleDenominator) {
11343             var scale = feature.layer.map.getScale();
11344         }
11345         
11346         // check if within minScale/maxScale bounds
11347         if (this.minScaleDenominator) {
11348             applies = scale >= OpenLayers.Style.createLiteral(
11349                     this.minScaleDenominator, context);
11350         }
11351         if (applies && this.maxScaleDenominator) {
11352             applies = scale < OpenLayers.Style.createLiteral(
11353                     this.maxScaleDenominator, context);
11354         }
11355         
11356         // check if optional filter applies
11357         if(applies && this.filter) {
11358             // feature id filters get the feature, others get the context
11359             if(this.filter.CLASS_NAME == "OpenLayers.Filter.FeatureId") {
11360                 applies = this.filter.evaluate(feature);
11361             } else {
11362                 applies = this.filter.evaluate(context);
11363             }
11364         }
11365
11366         return applies;
11367     },
11368     
11369     /**
11370      * Method: getContext
11371      * Gets the context for evaluating this rule
11372      * 
11373      * Parameters:
11374      * feature - {<OpenLayers.Feature>} feature to take the context from if
11375      *           none is specified.
11376      */
11377     getContext: function(feature) {
11378         var context = this.context;
11379         if (!context) {
11380             context = feature.attributes || feature.data;
11381         }
11382         if (typeof this.context == "function") {
11383             context = this.context(feature);
11384         }
11385         return context;
11386     },
11387     
11388     /**
11389      * APIMethod: clone
11390      * Clones this rule.
11391      * 
11392      * Returns:
11393      * {<OpenLayers.Rule>} Clone of this rule.
11394      */
11395     clone: function() {
11396         var options = OpenLayers.Util.extend({}, this);
11397         if (this.symbolizers) {
11398             // clone symbolizers
11399             var len = this.symbolizers.length;
11400             options.symbolizers = new Array(len);
11401             for (var i=0; i<len; ++i) {
11402                 options.symbolizers[i] = this.symbolizers[i].clone();
11403             }
11404         } else {
11405             // clone symbolizer
11406             options.symbolizer = {};
11407             var value, type;
11408             for(var key in this.symbolizer) {
11409                 value = this.symbolizer[key];
11410                 type = typeof value;
11411                 if(type === "object") {
11412                     options.symbolizer[key] = OpenLayers.Util.extend({}, value);
11413                 } else if(type === "string") {
11414                     options.symbolizer[key] = value;
11415                 }
11416             }
11417         }
11418         // clone filter
11419         options.filter = this.filter && this.filter.clone();
11420         // clone context
11421         options.context = this.context && OpenLayers.Util.extend({}, this.context);
11422         return new OpenLayers.Rule(options);
11423     },
11424         
11425     CLASS_NAME: "OpenLayers.Rule"
11426 });
11427 /* ======================================================================
11428     OpenLayers/StyleMap.js
11429    ====================================================================== */
11430
11431 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
11432  * full list of contributors). Published under the 2-clause BSD license.
11433  * See license.txt in the OpenLayers distribution or repository for the
11434  * full text of the license. */
11435
11436 /**
11437  * @requires OpenLayers/BaseTypes/Class.js
11438  * @requires OpenLayers/Style.js
11439  * @requires OpenLayers/Feature/Vector.js
11440  */
11441  
11442 /**
11443  * Class: OpenLayers.StyleMap
11444  */
11445 OpenLayers.StyleMap = OpenLayers.Class({
11446     
11447     /**
11448      * Property: styles
11449      * {Object} Hash of {<OpenLayers.Style>}, keyed by names of well known
11450      * rendering intents (e.g. "default", "temporary", "select", "delete").
11451      */
11452     styles: null,
11453     
11454     /**
11455      * Property: extendDefault
11456      * {Boolean} if true, every render intent will extend the symbolizers
11457      * specified for the "default" intent at rendering time. Otherwise, every
11458      * rendering intent will be treated as a completely independent style.
11459      */
11460     extendDefault: true,
11461     
11462     /**
11463      * Constructor: OpenLayers.StyleMap
11464      * 
11465      * Parameters:
11466      * style   - {Object} Optional. Either a style hash, or a style object, or
11467      *           a hash of style objects (style hashes) keyed by rendering
11468      *           intent. If just one style hash or style object is passed,
11469      *           this will be used for all known render intents (default,
11470      *           select, temporary)
11471      * options - {Object} optional hash of additional options for this
11472      *           instance
11473      */
11474     initialize: function (style, options) {
11475         this.styles = {
11476             "default": new OpenLayers.Style(
11477                 OpenLayers.Feature.Vector.style["default"]),
11478             "select": new OpenLayers.Style(
11479                 OpenLayers.Feature.Vector.style["select"]),
11480             "temporary": new OpenLayers.Style(
11481                 OpenLayers.Feature.Vector.style["temporary"]),
11482             "delete": new OpenLayers.Style(
11483                 OpenLayers.Feature.Vector.style["delete"])
11484         };
11485         
11486         // take whatever the user passed as style parameter and convert it
11487         // into parts of stylemap.
11488         if(style instanceof OpenLayers.Style) {
11489             // user passed a style object
11490             this.styles["default"] = style;
11491             this.styles["select"] = style;
11492             this.styles["temporary"] = style;
11493             this.styles["delete"] = style;
11494         } else if(typeof style == "object") {
11495             for(var key in style) {
11496                 if(style[key] instanceof OpenLayers.Style) {
11497                     // user passed a hash of style objects
11498                     this.styles[key] = style[key];
11499                 } else if(typeof style[key] == "object") {
11500                     // user passsed a hash of style hashes
11501                     this.styles[key] = new OpenLayers.Style(style[key]);
11502                 } else {
11503                     // user passed a style hash (i.e. symbolizer)
11504                     this.styles["default"] = new OpenLayers.Style(style);
11505                     this.styles["select"] = new OpenLayers.Style(style);
11506                     this.styles["temporary"] = new OpenLayers.Style(style);
11507                     this.styles["delete"] = new OpenLayers.Style(style);
11508                     break;
11509                 }
11510             }
11511         }
11512         OpenLayers.Util.extend(this, options);
11513     },
11514
11515     /**
11516      * Method: destroy
11517      */
11518     destroy: function() {
11519         for(var key in this.styles) {
11520             this.styles[key].destroy();
11521         }
11522         this.styles = null;
11523     },
11524     
11525     /**
11526      * Method: createSymbolizer
11527      * Creates the symbolizer for a feature for a render intent.
11528      * 
11529      * Parameters:
11530      * feature - {<OpenLayers.Feature>} The feature to evaluate the rules
11531      *           of the intended style against.
11532      * intent  - {String} The intent determines the symbolizer that will be
11533      *           used to draw the feature. Well known intents are "default"
11534      *           (for just drawing the features), "select" (for selected
11535      *           features) and "temporary" (for drawing features).
11536      * 
11537      * Returns:
11538      * {Object} symbolizer hash
11539      */
11540     createSymbolizer: function(feature, intent) {
11541         if(!feature) {
11542             feature = new OpenLayers.Feature.Vector();
11543         }
11544         if(!this.styles[intent]) {
11545             intent = "default";
11546         }
11547         feature.renderIntent = intent;
11548         var defaultSymbolizer = {};
11549         if(this.extendDefault && intent != "default") {
11550             defaultSymbolizer = this.styles["default"].createSymbolizer(feature);
11551         }
11552         return OpenLayers.Util.extend(defaultSymbolizer,
11553             this.styles[intent].createSymbolizer(feature));
11554     },
11555     
11556     /**
11557      * Method: addUniqueValueRules
11558      * Convenience method to create comparison rules for unique values of a
11559      * property. The rules will be added to the style object for a specified
11560      * rendering intent. This method is a shortcut for creating something like
11561      * the "unique value legends" familiar from well known desktop GIS systems
11562      * 
11563      * Parameters:
11564      * renderIntent - {String} rendering intent to add the rules to
11565      * property     - {String} values of feature attributes to create the
11566      *                rules for
11567      * symbolizers  - {Object} Hash of symbolizers, keyed by the desired
11568      *                property values 
11569      * context      - {Object} An optional object with properties that
11570      *                symbolizers' property values should be evaluated
11571      *                against. If no context is specified, feature.attributes
11572      *                will be used
11573      */
11574     addUniqueValueRules: function(renderIntent, property, symbolizers, context) {
11575         var rules = [];
11576         for (var value in symbolizers) {
11577             rules.push(new OpenLayers.Rule({
11578                 symbolizer: symbolizers[value],
11579                 context: context,
11580                 filter: new OpenLayers.Filter.Comparison({
11581                     type: OpenLayers.Filter.Comparison.EQUAL_TO,
11582                     property: property,
11583                     value: value
11584                 })
11585             }));
11586         }
11587         this.styles[renderIntent].addRules(rules);
11588     },
11589
11590     CLASS_NAME: "OpenLayers.StyleMap"
11591 });
11592 /* ======================================================================
11593     OpenLayers/Request.js
11594    ====================================================================== */
11595
11596 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
11597  * full list of contributors). Published under the 2-clause BSD license.
11598  * See license.txt in the OpenLayers distribution or repository for the
11599  * full text of the license. */
11600
11601 /**
11602  * @requires OpenLayers/Events.js
11603  * @requires OpenLayers/Request/XMLHttpRequest.js
11604  */
11605
11606 /**
11607  * TODO: deprecate me
11608  * Use OpenLayers.Request.proxy instead.
11609  */
11610 OpenLayers.ProxyHost = "";
11611
11612 /**
11613  * Namespace: OpenLayers.Request
11614  * The OpenLayers.Request namespace contains convenience methods for working
11615  *     with XMLHttpRequests.  These methods work with a cross-browser
11616  *     W3C compliant <OpenLayers.Request.XMLHttpRequest> class.
11617  */
11618 if (!OpenLayers.Request) {
11619     /**
11620      * This allows for OpenLayers/Request/XMLHttpRequest.js to be included
11621      * before or after this script.
11622      */
11623     OpenLayers.Request = {};
11624 }
11625 OpenLayers.Util.extend(OpenLayers.Request, {
11626     
11627     /**
11628      * Constant: DEFAULT_CONFIG
11629      * {Object} Default configuration for all requests.
11630      */
11631     DEFAULT_CONFIG: {
11632         method: "GET",
11633         url: window.location.href,
11634         async: true,
11635         user: undefined,
11636         password: undefined,
11637         params: null,
11638         proxy: OpenLayers.ProxyHost,
11639         headers: {},
11640         data: null,
11641         callback: function() {},
11642         success: null,
11643         failure: null,
11644         scope: null
11645     },
11646     
11647     /**
11648      * Constant: URL_SPLIT_REGEX
11649      */
11650     URL_SPLIT_REGEX: /([^:]*:)\/\/([^:]*:?[^@]*@)?([^:\/\?]*):?([^\/\?]*)/,
11651     
11652     /**
11653      * APIProperty: events
11654      * {<OpenLayers.Events>} An events object that handles all 
11655      *     events on the {<OpenLayers.Request>} object.
11656      *
11657      * All event listeners will receive an event object with three properties:
11658      * request - {<OpenLayers.Request.XMLHttpRequest>} The request object.
11659      * config - {Object} The config object sent to the specific request method.
11660      * requestUrl - {String} The request url.
11661      * 
11662      * Supported event types:
11663      * complete - Triggered when we have a response from the request, if a
11664      *     listener returns false, no further response processing will take
11665      *     place.
11666      * success - Triggered when the HTTP response has a success code (200-299).
11667      * failure - Triggered when the HTTP response does not have a success code.
11668      */
11669     events: new OpenLayers.Events(this),
11670     
11671     /**
11672      * Method: makeSameOrigin
11673      * Using the specified proxy, returns a same origin url of the provided url.
11674      *
11675      * Parameters:
11676      * url - {String} An arbitrary url
11677      * proxy {String|Function} The proxy to use to make the provided url a
11678      *     same origin url.
11679      *
11680      * Returns
11681      * {String} the same origin url. If no proxy is provided, the returned url
11682      *     will be the same as the provided url.
11683      */
11684     makeSameOrigin: function(url, proxy) {
11685         var sameOrigin = url.indexOf("http") !== 0;
11686         var urlParts = !sameOrigin && url.match(this.URL_SPLIT_REGEX);
11687         if (urlParts) {
11688             var location = window.location;
11689             sameOrigin =
11690                 urlParts[1] == location.protocol &&
11691                 urlParts[3] == location.hostname;
11692             var uPort = urlParts[4], lPort = location.port;
11693             if (uPort != 80 && uPort != "" || lPort != "80" && lPort != "") {
11694                 sameOrigin = sameOrigin && uPort == lPort;
11695             }
11696         }
11697         if (!sameOrigin) {
11698             if (proxy) {
11699                 if (typeof proxy == "function") {
11700                     url = proxy(url);
11701                 } else {
11702                     url = proxy + encodeURIComponent(url);
11703                 }
11704             }
11705         }
11706         return url;
11707     },
11708
11709     /**
11710      * APIMethod: issue
11711      * Create a new XMLHttpRequest object, open it, set any headers, bind
11712      *     a callback to done state, and send any data.  It is recommended that
11713      *     you use one <GET>, <POST>, <PUT>, <DELETE>, <OPTIONS>, or <HEAD>.
11714      *     This method is only documented to provide detail on the configuration
11715      *     options available to all request methods.
11716      *
11717      * Parameters:
11718      * config - {Object} Object containing properties for configuring the
11719      *     request.  Allowed configuration properties are described below.
11720      *     This object is modified and should not be reused.
11721      *
11722      * Allowed config properties:
11723      * method - {String} One of GET, POST, PUT, DELETE, HEAD, or
11724      *     OPTIONS.  Default is GET.
11725      * url - {String} URL for the request.
11726      * async - {Boolean} Open an asynchronous request.  Default is true.
11727      * user - {String} User for relevant authentication scheme.  Set
11728      *     to null to clear current user.
11729      * password - {String} Password for relevant authentication scheme.
11730      *     Set to null to clear current password.
11731      * proxy - {String} Optional proxy.  Defaults to
11732      *     <OpenLayers.ProxyHost>.
11733      * params - {Object} Any key:value pairs to be appended to the
11734      *     url as a query string.  Assumes url doesn't already include a query
11735      *     string or hash.  Typically, this is only appropriate for <GET>
11736      *     requests where the query string will be appended to the url.
11737      *     Parameter values that are arrays will be
11738      *     concatenated with a comma (note that this goes against form-encoding)
11739      *     as is done with <OpenLayers.Util.getParameterString>.
11740      * headers - {Object} Object with header:value pairs to be set on
11741      *     the request.
11742      * data - {String | Document} Optional data to send with the request.
11743      *     Typically, this is only used with <POST> and <PUT> requests.
11744      *     Make sure to provide the appropriate "Content-Type" header for your
11745      *     data.  For <POST> and <PUT> requests, the content type defaults to
11746      *     "application-xml".  If your data is a different content type, or
11747      *     if you are using a different HTTP method, set the "Content-Type"
11748      *     header to match your data type.
11749      * callback - {Function} Function to call when request is done.
11750      *     To determine if the request failed, check request.status (200
11751      *     indicates success).
11752      * success - {Function} Optional function to call if request status is in
11753      *     the 200s.  This will be called in addition to callback above and
11754      *     would typically only be used as an alternative.
11755      * failure - {Function} Optional function to call if request status is not
11756      *     in the 200s.  This will be called in addition to callback above and
11757      *     would typically only be used as an alternative.
11758      * scope - {Object} If callback is a public method on some object,
11759      *     set the scope to that object.
11760      *
11761      * Returns:
11762      * {XMLHttpRequest} Request object.  To abort the request before a response
11763      *     is received, call abort() on the request object.
11764      */
11765     issue: function(config) {        
11766         // apply default config - proxy host may have changed
11767         var defaultConfig = OpenLayers.Util.extend(
11768             this.DEFAULT_CONFIG,
11769             {proxy: OpenLayers.ProxyHost}
11770         );
11771         config = config || {};
11772         config.headers = config.headers || {};
11773         config = OpenLayers.Util.applyDefaults(config, defaultConfig);
11774         config.headers = OpenLayers.Util.applyDefaults(config.headers, defaultConfig.headers);
11775         // Always set the "X-Requested-With" header to signal that this request
11776         // was issued through the XHR-object. Since header keys are case 
11777         // insensitive and we want to allow overriding of the "X-Requested-With"
11778         // header through the user we cannot use applyDefaults, but have to 
11779         // check manually whether we were called with a "X-Requested-With"
11780         // header.
11781         var customRequestedWithHeader = false,
11782             headerKey;
11783         for(headerKey in config.headers) {
11784             if (config.headers.hasOwnProperty( headerKey )) {
11785                 if (headerKey.toLowerCase() === 'x-requested-with') {
11786                     customRequestedWithHeader = true;
11787                 }
11788             }
11789         }
11790         if (customRequestedWithHeader === false) {
11791             // we did not have a custom "X-Requested-With" header
11792             config.headers['X-Requested-With'] = 'XMLHttpRequest';
11793         }
11794
11795         // create request, open, and set headers
11796         var request = new OpenLayers.Request.XMLHttpRequest();
11797         var url = OpenLayers.Util.urlAppend(config.url, 
11798             OpenLayers.Util.getParameterString(config.params || {}));
11799         url = OpenLayers.Request.makeSameOrigin(url, config.proxy);
11800         request.open(
11801             config.method, url, config.async, config.user, config.password
11802         );
11803         for(var header in config.headers) {
11804             request.setRequestHeader(header, config.headers[header]);
11805         }
11806
11807         var events = this.events;
11808
11809         // we want to execute runCallbacks with "this" as the
11810         // execution scope
11811         var self = this;
11812         
11813         request.onreadystatechange = function() {
11814             if(request.readyState == OpenLayers.Request.XMLHttpRequest.DONE) {
11815                 var proceed = events.triggerEvent(
11816                     "complete",
11817                     {request: request, config: config, requestUrl: url}
11818                 );
11819                 if(proceed !== false) {
11820                     self.runCallbacks(
11821                         {request: request, config: config, requestUrl: url}
11822                     );
11823                 }
11824             }
11825         };
11826         
11827         // send request (optionally with data) and return
11828         // call in a timeout for asynchronous requests so the return is
11829         // available before readyState == 4 for cached docs
11830         if(config.async === false) {
11831             request.send(config.data);
11832         } else {
11833             window.setTimeout(function(){
11834                 if (request.readyState !== 0) { // W3C: 0-UNSENT
11835                     request.send(config.data);
11836                 }
11837             }, 0);
11838         }
11839         return request;
11840     },
11841     
11842     /**
11843      * Method: runCallbacks
11844      * Calls the complete, success and failure callbacks. Application
11845      *    can listen to the "complete" event, have the listener 
11846      *    display a confirm window and always return false, and
11847      *    execute OpenLayers.Request.runCallbacks if the user
11848      *    hits "yes" in the confirm window.
11849      *
11850      * Parameters:
11851      * options - {Object} Hash containing request, config and requestUrl keys
11852      */
11853     runCallbacks: function(options) {
11854         var request = options.request;
11855         var config = options.config;
11856         
11857         // bind callbacks to readyState 4 (done)
11858         var complete = (config.scope) ?
11859             OpenLayers.Function.bind(config.callback, config.scope) :
11860             config.callback;
11861         
11862         // optional success callback
11863         var success;
11864         if(config.success) {
11865             success = (config.scope) ?
11866                 OpenLayers.Function.bind(config.success, config.scope) :
11867                 config.success;
11868         }
11869
11870         // optional failure callback
11871         var failure;
11872         if(config.failure) {
11873             failure = (config.scope) ?
11874                 OpenLayers.Function.bind(config.failure, config.scope) :
11875                 config.failure;
11876         }
11877
11878         if (OpenLayers.Util.createUrlObject(config.url).protocol == "file:" &&
11879                                                         request.responseText) {
11880             request.status = 200;
11881         }
11882         complete(request);
11883
11884         if (!request.status || (request.status >= 200 && request.status < 300)) {
11885             this.events.triggerEvent("success", options);
11886             if(success) {
11887                 success(request);
11888             }
11889         }
11890         if(request.status && (request.status < 200 || request.status >= 300)) {                    
11891             this.events.triggerEvent("failure", options);
11892             if(failure) {
11893                 failure(request);
11894             }
11895         }
11896     },
11897     
11898     /**
11899      * APIMethod: GET
11900      * Send an HTTP GET request.  Additional configuration properties are
11901      *     documented in the <issue> method, with the method property set
11902      *     to GET.
11903      *
11904      * Parameters:
11905      * config - {Object} Object with properties for configuring the request.
11906      *     See the <issue> method for documentation of allowed properties.
11907      *     This object is modified and should not be reused.
11908      * 
11909      * Returns:
11910      * {XMLHttpRequest} Request object.
11911      */
11912     GET: function(config) {
11913         config = OpenLayers.Util.extend(config, {method: "GET"});
11914         return OpenLayers.Request.issue(config);
11915     },
11916     
11917     /**
11918      * APIMethod: POST
11919      * Send a POST request.  Additional configuration properties are
11920      *     documented in the <issue> method, with the method property set
11921      *     to POST and "Content-Type" header set to "application/xml".
11922      *
11923      * Parameters:
11924      * config - {Object} Object with properties for configuring the request.
11925      *     See the <issue> method for documentation of allowed properties.  The
11926      *     default "Content-Type" header will be set to "application-xml" if
11927      *     none is provided.  This object is modified and should not be reused.
11928      * 
11929      * Returns:
11930      * {XMLHttpRequest} Request object.
11931      */
11932     POST: function(config) {
11933         config = OpenLayers.Util.extend(config, {method: "POST"});
11934         // set content type to application/xml if it isn't already set
11935         config.headers = config.headers ? config.headers : {};
11936         if(!("CONTENT-TYPE" in OpenLayers.Util.upperCaseObject(config.headers))) {
11937             config.headers["Content-Type"] = "application/xml";
11938         }
11939         return OpenLayers.Request.issue(config);
11940     },
11941     
11942     /**
11943      * APIMethod: PUT
11944      * Send an HTTP PUT request.  Additional configuration properties are
11945      *     documented in the <issue> method, with the method property set
11946      *     to PUT and "Content-Type" header set to "application/xml".
11947      *
11948      * Parameters:
11949      * config - {Object} Object with properties for configuring the request.
11950      *     See the <issue> method for documentation of allowed properties.  The
11951      *     default "Content-Type" header will be set to "application-xml" if
11952      *     none is provided.  This object is modified and should not be reused.
11953      * 
11954      * Returns:
11955      * {XMLHttpRequest} Request object.
11956      */
11957     PUT: function(config) {
11958         config = OpenLayers.Util.extend(config, {method: "PUT"});
11959         // set content type to application/xml if it isn't already set
11960         config.headers = config.headers ? config.headers : {};
11961         if(!("CONTENT-TYPE" in OpenLayers.Util.upperCaseObject(config.headers))) {
11962             config.headers["Content-Type"] = "application/xml";
11963         }
11964         return OpenLayers.Request.issue(config);
11965     },
11966     
11967     /**
11968      * APIMethod: DELETE
11969      * Send an HTTP DELETE request.  Additional configuration properties are
11970      *     documented in the <issue> method, with the method property set
11971      *     to DELETE.
11972      *
11973      * Parameters:
11974      * config - {Object} Object with properties for configuring the request.
11975      *     See the <issue> method for documentation of allowed properties.
11976      *     This object is modified and should not be reused.
11977      * 
11978      * Returns:
11979      * {XMLHttpRequest} Request object.
11980      */
11981     DELETE: function(config) {
11982         config = OpenLayers.Util.extend(config, {method: "DELETE"});
11983         return OpenLayers.Request.issue(config);
11984     },
11985   
11986     /**
11987      * APIMethod: HEAD
11988      * Send an HTTP HEAD request.  Additional configuration properties are
11989      *     documented in the <issue> method, with the method property set
11990      *     to HEAD.
11991      *
11992      * Parameters:
11993      * config - {Object} Object with properties for configuring the request.
11994      *     See the <issue> method for documentation of allowed properties.
11995      *     This object is modified and should not be reused.
11996      * 
11997      * Returns:
11998      * {XMLHttpRequest} Request object.
11999      */
12000     HEAD: function(config) {
12001         config = OpenLayers.Util.extend(config, {method: "HEAD"});
12002         return OpenLayers.Request.issue(config);
12003     },
12004     
12005     /**
12006      * APIMethod: OPTIONS
12007      * Send an HTTP OPTIONS request.  Additional configuration properties are
12008      *     documented in the <issue> method, with the method property set
12009      *     to OPTIONS.
12010      *
12011      * Parameters:
12012      * config - {Object} Object with properties for configuring the request.
12013      *     See the <issue> method for documentation of allowed properties.
12014      *     This object is modified and should not be reused.
12015      * 
12016      * Returns:
12017      * {XMLHttpRequest} Request object.
12018      */
12019     OPTIONS: function(config) {
12020         config = OpenLayers.Util.extend(config, {method: "OPTIONS"});
12021         return OpenLayers.Request.issue(config);
12022     }
12023
12024 });
12025 /* ======================================================================
12026     OpenLayers/Request/XMLHttpRequest.js
12027    ====================================================================== */
12028
12029 // XMLHttpRequest.js Copyright (C) 2010 Sergey Ilinsky (http://www.ilinsky.com)
12030 //
12031 // Licensed under the Apache License, Version 2.0 (the "License");
12032 // you may not use this file except in compliance with the License.
12033 // You may obtain a copy of the License at
12034 //
12035 //   http://www.apache.org/licenses/LICENSE-2.0
12036 //
12037 // Unless required by applicable law or agreed to in writing, software
12038 // distributed under the License is distributed on an "AS IS" BASIS,
12039 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12040 // See the License for the specific language governing permissions and
12041 // limitations under the License.
12042
12043 /**
12044  * @requires OpenLayers/Request.js
12045  */
12046
12047 (function () {
12048
12049     // Save reference to earlier defined object implementation (if any)
12050     var oXMLHttpRequest    = window.XMLHttpRequest;
12051
12052     // Define on browser type
12053     var bGecko    = !!window.controllers,
12054         bIE        = window.document.all && !window.opera,
12055         bIE7    = bIE && window.navigator.userAgent.match(/MSIE 7.0/);
12056
12057     // Enables "XMLHttpRequest()" call next to "new XMLHttpReques()"
12058     function fXMLHttpRequest() {
12059         this._object    = oXMLHttpRequest && !bIE7 ? new oXMLHttpRequest : new window.ActiveXObject("Microsoft.XMLHTTP");
12060         this._listeners    = [];
12061     };
12062
12063     // Constructor
12064     function cXMLHttpRequest() {
12065         return new fXMLHttpRequest;
12066     };
12067     cXMLHttpRequest.prototype    = fXMLHttpRequest.prototype;
12068
12069     // BUGFIX: Firefox with Firebug installed would break pages if not executed
12070     if (bGecko && oXMLHttpRequest.wrapped)
12071         cXMLHttpRequest.wrapped    = oXMLHttpRequest.wrapped;
12072
12073     // Constants
12074     cXMLHttpRequest.UNSENT                = 0;
12075     cXMLHttpRequest.OPENED                = 1;
12076     cXMLHttpRequest.HEADERS_RECEIVED    = 2;
12077     cXMLHttpRequest.LOADING                = 3;
12078     cXMLHttpRequest.DONE                = 4;
12079
12080     // Public Properties
12081     cXMLHttpRequest.prototype.readyState    = cXMLHttpRequest.UNSENT;
12082     cXMLHttpRequest.prototype.responseText    = '';
12083     cXMLHttpRequest.prototype.responseXML    = null;
12084     cXMLHttpRequest.prototype.status        = 0;
12085     cXMLHttpRequest.prototype.statusText    = '';
12086
12087     // Priority proposal
12088     cXMLHttpRequest.prototype.priority        = "NORMAL";
12089
12090     // Instance-level Events Handlers
12091     cXMLHttpRequest.prototype.onreadystatechange    = null;
12092
12093     // Class-level Events Handlers
12094     cXMLHttpRequest.onreadystatechange    = null;
12095     cXMLHttpRequest.onopen                = null;
12096     cXMLHttpRequest.onsend                = null;
12097     cXMLHttpRequest.onabort                = null;
12098
12099     // Public Methods
12100     cXMLHttpRequest.prototype.open    = function(sMethod, sUrl, bAsync, sUser, sPassword) {
12101         // Delete headers, required when object is reused
12102         delete this._headers;
12103
12104         // When bAsync parameter value is omitted, use true as default
12105         if (arguments.length < 3)
12106             bAsync    = true;
12107
12108         // Save async parameter for fixing Gecko bug with missing readystatechange in synchronous requests
12109         this._async        = bAsync;
12110
12111         // Set the onreadystatechange handler
12112         var oRequest    = this,
12113             nState        = this.readyState,
12114             fOnUnload;
12115
12116         // BUGFIX: IE - memory leak on page unload (inter-page leak)
12117         if (bIE && bAsync) {
12118             fOnUnload = function() {
12119                 if (nState != cXMLHttpRequest.DONE) {
12120                     fCleanTransport(oRequest);
12121                     // Safe to abort here since onreadystatechange handler removed
12122                     oRequest.abort();
12123                 }
12124             };
12125             window.attachEvent("onunload", fOnUnload);
12126         }
12127
12128         // Add method sniffer
12129         if (cXMLHttpRequest.onopen)
12130             cXMLHttpRequest.onopen.apply(this, arguments);
12131
12132         if (arguments.length > 4)
12133             this._object.open(sMethod, sUrl, bAsync, sUser, sPassword);
12134         else
12135         if (arguments.length > 3)
12136             this._object.open(sMethod, sUrl, bAsync, sUser);
12137         else
12138             this._object.open(sMethod, sUrl, bAsync);
12139
12140         this.readyState    = cXMLHttpRequest.OPENED;
12141         fReadyStateChange(this);
12142
12143         this._object.onreadystatechange    = function() {
12144             if (bGecko && !bAsync)
12145                 return;
12146
12147             // Synchronize state
12148             oRequest.readyState        = oRequest._object.readyState;
12149
12150             //
12151             fSynchronizeValues(oRequest);
12152
12153             // BUGFIX: Firefox fires unnecessary DONE when aborting
12154             if (oRequest._aborted) {
12155                 // Reset readyState to UNSENT
12156                 oRequest.readyState    = cXMLHttpRequest.UNSENT;
12157
12158                 // Return now
12159                 return;
12160             }
12161
12162             if (oRequest.readyState == cXMLHttpRequest.DONE) {
12163                 // Free up queue
12164                 delete oRequest._data;
12165 /*                if (bAsync)
12166                     fQueue_remove(oRequest);*/
12167                 //
12168                 fCleanTransport(oRequest);
12169 // Uncomment this block if you need a fix for IE cache
12170 /*
12171                 // BUGFIX: IE - cache issue
12172                 if (!oRequest._object.getResponseHeader("Date")) {
12173                     // Save object to cache
12174                     oRequest._cached    = oRequest._object;
12175
12176                     // Instantiate a new transport object
12177                     cXMLHttpRequest.call(oRequest);
12178
12179                     // Re-send request
12180                     if (sUser) {
12181                          if (sPassword)
12182                             oRequest._object.open(sMethod, sUrl, bAsync, sUser, sPassword);
12183                         else
12184                             oRequest._object.open(sMethod, sUrl, bAsync, sUser);
12185                     }
12186                     else
12187                         oRequest._object.open(sMethod, sUrl, bAsync);
12188                     oRequest._object.setRequestHeader("If-Modified-Since", oRequest._cached.getResponseHeader("Last-Modified") || new window.Date(0));
12189                     // Copy headers set
12190                     if (oRequest._headers)
12191                         for (var sHeader in oRequest._headers)
12192                             if (typeof oRequest._headers[sHeader] == "string")    // Some frameworks prototype objects with functions
12193                                 oRequest._object.setRequestHeader(sHeader, oRequest._headers[sHeader]);
12194
12195                     oRequest._object.onreadystatechange    = function() {
12196                         // Synchronize state
12197                         oRequest.readyState        = oRequest._object.readyState;
12198
12199                         if (oRequest._aborted) {
12200                             //
12201                             oRequest.readyState    = cXMLHttpRequest.UNSENT;
12202
12203                             // Return
12204                             return;
12205                         }
12206
12207                         if (oRequest.readyState == cXMLHttpRequest.DONE) {
12208                             // Clean Object
12209                             fCleanTransport(oRequest);
12210
12211                             // get cached request
12212                             if (oRequest.status == 304)
12213                                 oRequest._object    = oRequest._cached;
12214
12215                             //
12216                             delete oRequest._cached;
12217
12218                             //
12219                             fSynchronizeValues(oRequest);
12220
12221                             //
12222                             fReadyStateChange(oRequest);
12223
12224                             // BUGFIX: IE - memory leak in interrupted
12225                             if (bIE && bAsync)
12226                                 window.detachEvent("onunload", fOnUnload);
12227                         }
12228                     };
12229                     oRequest._object.send(null);
12230
12231                     // Return now - wait until re-sent request is finished
12232                     return;
12233                 };
12234 */
12235                 // BUGFIX: IE - memory leak in interrupted
12236                 if (bIE && bAsync)
12237                     window.detachEvent("onunload", fOnUnload);
12238             }
12239
12240             // BUGFIX: Some browsers (Internet Explorer, Gecko) fire OPEN readystate twice
12241             if (nState != oRequest.readyState)
12242                 fReadyStateChange(oRequest);
12243
12244             nState    = oRequest.readyState;
12245         }
12246     };
12247     function fXMLHttpRequest_send(oRequest) {
12248         oRequest._object.send(oRequest._data);
12249
12250         // BUGFIX: Gecko - missing readystatechange calls in synchronous requests
12251         if (bGecko && !oRequest._async) {
12252             oRequest.readyState    = cXMLHttpRequest.OPENED;
12253
12254             // Synchronize state
12255             fSynchronizeValues(oRequest);
12256
12257             // Simulate missing states
12258             while (oRequest.readyState < cXMLHttpRequest.DONE) {
12259                 oRequest.readyState++;
12260                 fReadyStateChange(oRequest);
12261                 // Check if we are aborted
12262                 if (oRequest._aborted)
12263                     return;
12264             }
12265         }
12266     };
12267     cXMLHttpRequest.prototype.send    = function(vData) {
12268         // Add method sniffer
12269         if (cXMLHttpRequest.onsend)
12270             cXMLHttpRequest.onsend.apply(this, arguments);
12271
12272         if (!arguments.length)
12273             vData    = null;
12274
12275         // BUGFIX: Safari - fails sending documents created/modified dynamically, so an explicit serialization required
12276         // BUGFIX: IE - rewrites any custom mime-type to "text/xml" in case an XMLNode is sent
12277         // BUGFIX: Gecko - fails sending Element (this is up to the implementation either to standard)
12278         if (vData && vData.nodeType) {
12279             vData    = window.XMLSerializer ? new window.XMLSerializer().serializeToString(vData) : vData.xml;
12280             if (!this._headers["Content-Type"])
12281                 this._object.setRequestHeader("Content-Type", "application/xml");
12282         }
12283
12284         this._data    = vData;
12285 /*
12286         // Add to queue
12287         if (this._async)
12288             fQueue_add(this);
12289         else*/
12290             fXMLHttpRequest_send(this);
12291     };
12292     cXMLHttpRequest.prototype.abort    = function() {
12293         // Add method sniffer
12294         if (cXMLHttpRequest.onabort)
12295             cXMLHttpRequest.onabort.apply(this, arguments);
12296
12297         // BUGFIX: Gecko - unnecessary DONE when aborting
12298         if (this.readyState > cXMLHttpRequest.UNSENT)
12299             this._aborted    = true;
12300
12301         this._object.abort();
12302
12303         // BUGFIX: IE - memory leak
12304         fCleanTransport(this);
12305
12306         this.readyState    = cXMLHttpRequest.UNSENT;
12307
12308         delete this._data;
12309 /*        if (this._async)
12310             fQueue_remove(this);*/
12311     };
12312     cXMLHttpRequest.prototype.getAllResponseHeaders    = function() {
12313         return this._object.getAllResponseHeaders();
12314     };
12315     cXMLHttpRequest.prototype.getResponseHeader    = function(sName) {
12316         return this._object.getResponseHeader(sName);
12317     };
12318     cXMLHttpRequest.prototype.setRequestHeader    = function(sName, sValue) {
12319         // BUGFIX: IE - cache issue
12320         if (!this._headers)
12321             this._headers    = {};
12322         this._headers[sName]    = sValue;
12323
12324         return this._object.setRequestHeader(sName, sValue);
12325     };
12326
12327     // EventTarget interface implementation
12328     cXMLHttpRequest.prototype.addEventListener    = function(sName, fHandler, bUseCapture) {
12329         for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++)
12330             if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture)
12331                 return;
12332         // Add listener
12333         this._listeners.push([sName, fHandler, bUseCapture]);
12334     };
12335
12336     cXMLHttpRequest.prototype.removeEventListener    = function(sName, fHandler, bUseCapture) {
12337         for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++)
12338             if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture)
12339                 break;
12340         // Remove listener
12341         if (oListener)
12342             this._listeners.splice(nIndex, 1);
12343     };
12344
12345     cXMLHttpRequest.prototype.dispatchEvent    = function(oEvent) {
12346         var oEventPseudo    = {
12347             'type':            oEvent.type,
12348             'target':        this,
12349             'currentTarget':this,
12350             'eventPhase':    2,
12351             'bubbles':        oEvent.bubbles,
12352             'cancelable':    oEvent.cancelable,
12353             'timeStamp':    oEvent.timeStamp,
12354             'stopPropagation':    function() {},    // There is no flow
12355             'preventDefault':    function() {},    // There is no default action
12356             'initEvent':        function() {}    // Original event object should be initialized
12357         };
12358
12359         // Execute onreadystatechange
12360         if (oEventPseudo.type == "readystatechange" && this.onreadystatechange)
12361             (this.onreadystatechange.handleEvent || this.onreadystatechange).apply(this, [oEventPseudo]);
12362
12363         // Execute listeners
12364         for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++)
12365             if (oListener[0] == oEventPseudo.type && !oListener[2])
12366                 (oListener[1].handleEvent || oListener[1]).apply(this, [oEventPseudo]);
12367     };
12368
12369     //
12370     cXMLHttpRequest.prototype.toString    = function() {
12371         return '[' + "object" + ' ' + "XMLHttpRequest" + ']';
12372     };
12373
12374     cXMLHttpRequest.toString    = function() {
12375         return '[' + "XMLHttpRequest" + ']';
12376     };
12377
12378     // Helper function
12379     function fReadyStateChange(oRequest) {
12380         // Sniffing code
12381         if (cXMLHttpRequest.onreadystatechange)
12382             cXMLHttpRequest.onreadystatechange.apply(oRequest);
12383
12384         // Fake event
12385         oRequest.dispatchEvent({
12386             'type':            "readystatechange",
12387             'bubbles':        false,
12388             'cancelable':    false,
12389             'timeStamp':    new Date + 0
12390         });
12391     };
12392
12393     function fGetDocument(oRequest) {
12394         var oDocument    = oRequest.responseXML,
12395             sResponse    = oRequest.responseText;
12396         // Try parsing responseText
12397         if (bIE && sResponse && oDocument && !oDocument.documentElement && oRequest.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/)) {
12398             oDocument    = new window.ActiveXObject("Microsoft.XMLDOM");
12399             oDocument.async                = false;
12400             oDocument.validateOnParse    = false;
12401             oDocument.loadXML(sResponse);
12402         }
12403         // Check if there is no error in document
12404         if (oDocument)
12405             if ((bIE && oDocument.parseError != 0) || !oDocument.documentElement || (oDocument.documentElement && oDocument.documentElement.tagName == "parsererror"))
12406                 return null;
12407         return oDocument;
12408     };
12409
12410     function fSynchronizeValues(oRequest) {
12411         try {    oRequest.responseText    = oRequest._object.responseText;    } catch (e) {}
12412         try {    oRequest.responseXML    = fGetDocument(oRequest._object);    } catch (e) {}
12413         try {    oRequest.status            = oRequest._object.status;            } catch (e) {}
12414         try {    oRequest.statusText        = oRequest._object.statusText;        } catch (e) {}
12415     };
12416
12417     function fCleanTransport(oRequest) {
12418         // BUGFIX: IE - memory leak (on-page leak)
12419         oRequest._object.onreadystatechange    = new window.Function;
12420     };
12421 /*
12422     // Queue manager
12423     var oQueuePending    = {"CRITICAL":[],"HIGH":[],"NORMAL":[],"LOW":[],"LOWEST":[]},
12424         aQueueRunning    = [];
12425     function fQueue_add(oRequest) {
12426         oQueuePending[oRequest.priority in oQueuePending ? oRequest.priority : "NORMAL"].push(oRequest);
12427         //
12428         setTimeout(fQueue_process);
12429     };
12430
12431     function fQueue_remove(oRequest) {
12432         for (var nIndex = 0, bFound    = false; nIndex < aQueueRunning.length; nIndex++)
12433             if (bFound)
12434                 aQueueRunning[nIndex - 1]    = aQueueRunning[nIndex];
12435             else
12436             if (aQueueRunning[nIndex] == oRequest)
12437                 bFound    = true;
12438         if (bFound)
12439             aQueueRunning.length--;
12440         //
12441         setTimeout(fQueue_process);
12442     };
12443
12444     function fQueue_process() {
12445         if (aQueueRunning.length < 6) {
12446             for (var sPriority in oQueuePending) {
12447                 if (oQueuePending[sPriority].length) {
12448                     var oRequest    = oQueuePending[sPriority][0];
12449                     oQueuePending[sPriority]    = oQueuePending[sPriority].slice(1);
12450                     //
12451                     aQueueRunning.push(oRequest);
12452                     // Send request
12453                     fXMLHttpRequest_send(oRequest);
12454                     break;
12455                 }
12456             }
12457         }
12458     };
12459 */
12460     // Internet Explorer 5.0 (missing apply)
12461     if (!window.Function.prototype.apply) {
12462         window.Function.prototype.apply    = function(oRequest, oArguments) {
12463             if (!oArguments)
12464                 oArguments    = [];
12465             oRequest.__func    = this;
12466             oRequest.__func(oArguments[0], oArguments[1], oArguments[2], oArguments[3], oArguments[4]);
12467             delete oRequest.__func;
12468         };
12469     };
12470
12471     // Register new object with window
12472     /**
12473      * Class: OpenLayers.Request.XMLHttpRequest
12474      * Standard-compliant (W3C) cross-browser implementation of the
12475      *     XMLHttpRequest object.  From
12476      *     http://code.google.com/p/xmlhttprequest/.
12477      */
12478     if (!OpenLayers.Request) {
12479         /**
12480          * This allows for OpenLayers/Request.js to be included
12481          * before or after this script.
12482          */
12483         OpenLayers.Request = {};
12484     }
12485     OpenLayers.Request.XMLHttpRequest = cXMLHttpRequest;
12486 })();
12487 /* ======================================================================
12488     OpenLayers/Protocol.js
12489    ====================================================================== */
12490
12491 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
12492  * full list of contributors). Published under the 2-clause BSD license.
12493  * See license.txt in the OpenLayers distribution or repository for the
12494  * full text of the license. */
12495
12496 /**
12497  * @requires OpenLayers/BaseTypes/Class.js
12498  */
12499
12500 /**
12501  * Class: OpenLayers.Protocol
12502  * Abstract vector layer protocol class.  Not to be instantiated directly.  Use
12503  *     one of the protocol subclasses instead.
12504  */
12505 OpenLayers.Protocol = OpenLayers.Class({
12506     
12507     /**
12508      * Property: format
12509      * {<OpenLayers.Format>} The format used by this protocol.
12510      */
12511     format: null,
12512     
12513     /**
12514      * Property: options
12515      * {Object} Any options sent to the constructor.
12516      */
12517     options: null,
12518
12519     /**
12520      * Property: autoDestroy
12521      * {Boolean} The creator of the protocol can set autoDestroy to false
12522      *      to fully control when the protocol is destroyed. Defaults to
12523      *      true.
12524      */
12525     autoDestroy: true,
12526    
12527     /**
12528      * Property: defaultFilter
12529      * {<OpenLayers.Filter>} Optional default filter to read requests
12530      */
12531     defaultFilter: null,
12532     
12533     /**
12534      * Constructor: OpenLayers.Protocol
12535      * Abstract class for vector protocols.  Create instances of a subclass.
12536      *
12537      * Parameters:
12538      * options - {Object} Optional object whose properties will be set on the
12539      *     instance.
12540      */
12541     initialize: function(options) {
12542         options = options || {};
12543         OpenLayers.Util.extend(this, options);
12544         this.options = options;
12545     },
12546
12547     /**
12548      * Method: mergeWithDefaultFilter
12549      * Merge filter passed to the read method with the default one
12550      *
12551      * Parameters:
12552      * filter - {<OpenLayers.Filter>}
12553      */
12554     mergeWithDefaultFilter: function(filter) {
12555         var merged;
12556         if (filter && this.defaultFilter) {
12557             merged = new OpenLayers.Filter.Logical({
12558                 type: OpenLayers.Filter.Logical.AND,
12559                 filters: [this.defaultFilter, filter]
12560             });
12561         } else {
12562             merged = filter || this.defaultFilter || undefined;
12563         }
12564         return merged;
12565     },
12566
12567     /**
12568      * APIMethod: destroy
12569      * Clean up the protocol.
12570      */
12571     destroy: function() {
12572         this.options = null;
12573         this.format = null;
12574     },
12575     
12576     /**
12577      * APIMethod: read
12578      * Construct a request for reading new features.
12579      *
12580      * Parameters:
12581      * options - {Object} Optional object for configuring the request.
12582      *
12583      * Returns:
12584      * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
12585      * object, the same object will be passed to the callback function passed
12586      * if one exists in the options object.
12587      */
12588     read: function(options) {
12589         options = options || {};
12590         options.filter = this.mergeWithDefaultFilter(options.filter);
12591     },
12592     
12593     
12594     /**
12595      * APIMethod: create
12596      * Construct a request for writing newly created features.
12597      *
12598      * Parameters:
12599      * features - {Array({<OpenLayers.Feature.Vector>})} or
12600      *            {<OpenLayers.Feature.Vector>}
12601      * options - {Object} Optional object for configuring the request.
12602      *
12603      * Returns:
12604      * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
12605      * object, the same object will be passed to the callback function passed
12606      * if one exists in the options object.
12607      */
12608     create: function() {
12609     },
12610     
12611     /**
12612      * APIMethod: update
12613      * Construct a request updating modified features.
12614      *
12615      * Parameters:
12616      * features - {Array({<OpenLayers.Feature.Vector>})} or
12617      *            {<OpenLayers.Feature.Vector>}
12618      * options - {Object} Optional object for configuring the request.
12619      *
12620      * Returns:
12621      * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
12622      * object, the same object will be passed to the callback function passed
12623      * if one exists in the options object.
12624      */
12625     update: function() {
12626     },
12627     
12628     /**
12629      * APIMethod: delete
12630      * Construct a request deleting a removed feature.
12631      *
12632      * Parameters:
12633      * feature - {<OpenLayers.Feature.Vector>}
12634      * options - {Object} Optional object for configuring the request.
12635      *
12636      * Returns:
12637      * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
12638      * object, the same object will be passed to the callback function passed
12639      * if one exists in the options object.
12640      */
12641     "delete": function() {
12642     },
12643
12644     /**
12645      * APIMethod: commit
12646      * Go over the features and for each take action
12647      * based on the feature state. Possible actions are create,
12648      * update and delete.
12649      *
12650      * Parameters:
12651      * features - {Array({<OpenLayers.Feature.Vector>})}
12652      * options - {Object} Object whose possible keys are "create", "update",
12653      *      "delete", "callback" and "scope", the values referenced by the
12654      *      first three are objects as passed to the "create", "update", and
12655      *      "delete" methods, the value referenced by the "callback" key is
12656      *      a function which is called when the commit operation is complete
12657      *      using the scope referenced by the "scope" key.
12658      *
12659      * Returns:
12660      * {Array({<OpenLayers.Protocol.Response>})} An array of
12661      * <OpenLayers.Protocol.Response> objects.
12662      */
12663     commit: function() {
12664     },
12665
12666     /**
12667      * Method: abort
12668      * Abort an ongoing request.
12669      *
12670      * Parameters:
12671      * response - {<OpenLayers.Protocol.Response>}
12672      */
12673     abort: function(response) {
12674     },
12675    
12676     /**
12677      * Method: createCallback
12678      * Returns a function that applies the given public method with resp and
12679      *     options arguments.
12680      *
12681      * Parameters:
12682      * method - {Function} The method to be applied by the callback.
12683      * response - {<OpenLayers.Protocol.Response>} The protocol response object.
12684      * options - {Object} Options sent to the protocol method
12685      */
12686     createCallback: function(method, response, options) {
12687         return OpenLayers.Function.bind(function() {
12688             method.apply(this, [response, options]);
12689         }, this);
12690     },
12691    
12692     CLASS_NAME: "OpenLayers.Protocol" 
12693 });
12694
12695 /**
12696  * Class: OpenLayers.Protocol.Response
12697  * Protocols return Response objects to their users.
12698  */
12699 OpenLayers.Protocol.Response = OpenLayers.Class({
12700     /**
12701      * Property: code
12702      * {Number} - OpenLayers.Protocol.Response.SUCCESS or
12703      *            OpenLayers.Protocol.Response.FAILURE
12704      */
12705     code: null,
12706
12707     /**
12708      * Property: requestType
12709      * {String} The type of request this response corresponds to. Either
12710      *      "create", "read", "update" or "delete".
12711      */
12712     requestType: null,
12713
12714     /**
12715      * Property: last
12716      * {Boolean} - true if this is the last response expected in a commit,
12717      * false otherwise, defaults to true.
12718      */
12719     last: true,
12720
12721     /**
12722      * Property: features
12723      * {Array({<OpenLayers.Feature.Vector>})} or {<OpenLayers.Feature.Vector>}
12724      * The features returned in the response by the server. Depending on the 
12725      * protocol's read payload, either features or data will be populated.
12726      */
12727     features: null,
12728
12729     /**
12730      * Property: data
12731      * {Object}
12732      * The data returned in the response by the server. Depending on the 
12733      * protocol's read payload, either features or data will be populated.
12734      */
12735     data: null,
12736
12737     /**
12738      * Property: reqFeatures
12739      * {Array({<OpenLayers.Feature.Vector>})} or {<OpenLayers.Feature.Vector>}
12740      * The features provided by the user and placed in the request by the
12741      *      protocol.
12742      */
12743     reqFeatures: null,
12744
12745     /**
12746      * Property: priv
12747      */
12748     priv: null,
12749
12750     /**
12751      * Property: error
12752      * {Object} The error object in case a service exception was encountered.
12753      */
12754     error: null,
12755
12756     /**
12757      * Constructor: OpenLayers.Protocol.Response
12758      *
12759      * Parameters:
12760      * options - {Object} Optional object whose properties will be set on the
12761      *     instance.
12762      */
12763     initialize: function(options) {
12764         OpenLayers.Util.extend(this, options);
12765     },
12766
12767     /**
12768      * Method: success
12769      *
12770      * Returns:
12771      * {Boolean} - true on success, false otherwise
12772      */
12773     success: function() {
12774         return this.code > 0;
12775     },
12776
12777     CLASS_NAME: "OpenLayers.Protocol.Response"
12778 });
12779
12780 OpenLayers.Protocol.Response.SUCCESS = 1;
12781 OpenLayers.Protocol.Response.FAILURE = 0;
12782 /* ======================================================================
12783     OpenLayers/Protocol/WFS.js
12784    ====================================================================== */
12785
12786 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
12787  * full list of contributors). Published under the 2-clause BSD license.
12788  * See license.txt in the OpenLayers distribution or repository for the
12789  * full text of the license. */
12790
12791 /**
12792  * @requires OpenLayers/Protocol.js
12793  */
12794
12795 /**
12796  * Class: OpenLayers.Protocol.WFS
12797  * Used to create a versioned WFS protocol.  Default version is 1.0.0.
12798  *
12799  * Returns:
12800  * {<OpenLayers.Protocol>} A WFS protocol of the given version.
12801  *
12802  * Example:
12803  * (code)
12804  *     var protocol = new OpenLayers.Protocol.WFS({
12805  *         version: "1.1.0",
12806  *         url:  "http://demo.opengeo.org/geoserver/wfs",
12807  *         featureType: "tasmania_roads",
12808  *         featureNS: "http://www.openplans.org/topp",
12809  *         geometryName: "the_geom"
12810  *     });
12811  * (end)
12812  *
12813  * See the protocols for specific WFS versions for more detail.
12814  */
12815 OpenLayers.Protocol.WFS = function(options) {
12816     options = OpenLayers.Util.applyDefaults(
12817         options, OpenLayers.Protocol.WFS.DEFAULTS
12818     );
12819     var cls = OpenLayers.Protocol.WFS["v"+options.version.replace(/\./g, "_")];
12820     if(!cls) {
12821         throw "Unsupported WFS version: " + options.version;
12822     }
12823     return new cls(options);
12824 };
12825
12826 /**
12827  * Function: fromWMSLayer
12828  * Convenience function to create a WFS protocol from a WMS layer.  This makes
12829  *     the assumption that a WFS requests can be issued at the same URL as
12830  *     WMS requests and that a WFS featureType exists with the same name as the
12831  *     WMS layer.
12832  *     
12833  * This function is designed to auto-configure <url>, <featureType>,
12834  *     <featurePrefix> and <srsName> for WFS <version> 1.1.0. Note that
12835  *     srsName matching with the WMS layer will not work with WFS 1.0.0.
12836  * 
12837  * Parameters:
12838  * layer - {<OpenLayers.Layer.WMS>} WMS layer that has a matching WFS
12839  *     FeatureType at the same server url with the same typename.
12840  * options - {Object} Default properties to be set on the protocol.
12841  *
12842  * Returns:
12843  * {<OpenLayers.Protocol.WFS>}
12844  */
12845 OpenLayers.Protocol.WFS.fromWMSLayer = function(layer, options) {
12846     var typeName, featurePrefix;
12847     var param = layer.params["LAYERS"];
12848     var parts = (OpenLayers.Util.isArray(param) ? param[0] : param).split(":");
12849     if(parts.length > 1) {
12850         featurePrefix = parts[0];
12851     }
12852     typeName = parts.pop();
12853     var protocolOptions = {
12854         url: layer.url,
12855         featureType: typeName,
12856         featurePrefix: featurePrefix,
12857         srsName: layer.projection && layer.projection.getCode() ||
12858                  layer.map && layer.map.getProjectionObject().getCode(),
12859         version: "1.1.0"
12860     };
12861     return new OpenLayers.Protocol.WFS(OpenLayers.Util.applyDefaults(
12862         options, protocolOptions
12863     ));
12864 };
12865
12866 /**
12867  * Constant: OpenLayers.Protocol.WFS.DEFAULTS
12868  */
12869 OpenLayers.Protocol.WFS.DEFAULTS = {
12870     "version": "1.0.0"
12871 };
12872 /* ======================================================================
12873     OpenLayers/Protocol/WFS/v1.js
12874    ====================================================================== */
12875
12876 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
12877  * full list of contributors). Published under the 2-clause BSD license.
12878  * See license.txt in the OpenLayers distribution or repository for the
12879  * full text of the license. */
12880
12881 /**
12882  * @requires OpenLayers/Protocol/WFS.js
12883  */
12884
12885 /**
12886  * Class: OpenLayers.Protocol.WFS.v1
12887  * Abstract class for for v1.0.0 and v1.1.0 protocol.
12888  *
12889  * Inherits from:
12890  *  - <OpenLayers.Protocol>
12891  */
12892 OpenLayers.Protocol.WFS.v1 = OpenLayers.Class(OpenLayers.Protocol, {
12893     
12894     /**
12895      * Property: version
12896      * {String} WFS version number.
12897      */
12898     version: null,
12899     
12900     /**
12901      * Property: srsName
12902      * {String} Name of spatial reference system.  Default is "EPSG:4326".
12903      */
12904     srsName: "EPSG:4326",
12905     
12906     /**
12907      * Property: featureType
12908      * {String} Local feature typeName.
12909      */
12910     featureType: null,
12911     
12912     /**
12913      * Property: featureNS
12914      * {String} Feature namespace.
12915      */
12916     featureNS: null,
12917     
12918     /**
12919      * Property: geometryName
12920      * {String} Name of the geometry attribute for features.  Default is
12921      *     "the_geom" for WFS <version> 1.0, and null for higher versions.
12922      */
12923     geometryName: "the_geom",
12924
12925     /**
12926      * Property: maxFeatures
12927      * {Integer} Optional maximum number of features to retrieve.
12928      */
12929     
12930     /**
12931      * Property: schema
12932      * {String} Optional schema location that will be included in the
12933      *     schemaLocation attribute value.  Note that the feature type schema
12934      *     is required for a strict XML validator (on transactions with an
12935      *     insert for example), but is *not* required by the WFS specification
12936      *     (since the server is supposed to know about feature type schemas).
12937      */
12938     schema: null,
12939
12940     /**
12941      * Property: featurePrefix
12942      * {String} Namespace alias for feature type.  Default is "feature".
12943      */
12944     featurePrefix: "feature",
12945     
12946     /**
12947      * Property: formatOptions
12948      * {Object} Optional options for the format.  If a format is not provided,
12949      *     this property can be used to extend the default format options.
12950      */
12951     formatOptions: null,
12952
12953     /** 
12954      * Property: readFormat 
12955      * {<OpenLayers.Format>} For WFS requests it is possible to get a  
12956      *     different output format than GML. In that case, we cannot parse  
12957      *     the response with the default format (WFST) and we need a different 
12958      *     format for reading. 
12959      */ 
12960     readFormat: null,
12961     
12962     /**
12963      * Property: readOptions
12964      * {Object} Optional object to pass to format's read.
12965      */
12966     readOptions: null,
12967     
12968     /**
12969      * Constructor: OpenLayers.Protocol.WFS
12970      * A class for giving layers WFS protocol.
12971      *
12972      * Parameters:
12973      * options - {Object} Optional object whose properties will be set on the
12974      *     instance.
12975      *
12976      * Valid options properties:
12977      * url - {String} URL to send requests to (required).
12978      * featureType - {String} Local (without prefix) feature typeName (required).
12979      * featureNS - {String} Feature namespace (required, but can be autodetected
12980      *     during the first query if GML is used as readFormat and
12981      *     featurePrefix is provided and matches the prefix used by the server
12982      *     for this featureType).
12983      * featurePrefix - {String} Feature namespace alias (optional - only used
12984      *     for writing if featureNS is provided).  Default is 'feature'.
12985      * geometryName - {String} Name of geometry attribute.  The default is
12986      *     'the_geom' for WFS <version> 1.0, and null for higher versions. If
12987      *     null, it will be set to the name of the first geometry found in the
12988      *     first read operation.
12989      * multi - {Boolean} If set to true, geometries will be casted to Multi
12990      *     geometries before they are written in a transaction. No casting will
12991      *     be done when reading features.
12992      */
12993     initialize: function(options) {
12994         OpenLayers.Protocol.prototype.initialize.apply(this, [options]);
12995         if(!options.format) {
12996             this.format = OpenLayers.Format.WFST(OpenLayers.Util.extend({
12997                 version: this.version,
12998                 featureType: this.featureType,
12999                 featureNS: this.featureNS,
13000                 featurePrefix: this.featurePrefix,
13001                 geometryName: this.geometryName,
13002                 srsName: this.srsName,
13003                 schema: this.schema
13004             }, this.formatOptions));
13005         }
13006         if (!options.geometryName && parseFloat(this.format.version) > 1.0) {
13007             this.setGeometryName(null);
13008         }
13009     },
13010     
13011     /**
13012      * APIMethod: destroy
13013      * Clean up the protocol.
13014      */
13015     destroy: function() {
13016         if(this.options && !this.options.format) {
13017             this.format.destroy();
13018         }
13019         this.format = null;
13020         OpenLayers.Protocol.prototype.destroy.apply(this);
13021     },
13022
13023     /**
13024      * APIMethod: read
13025      * Construct a request for reading new features.  Since WFS splits the
13026      *     basic CRUD operations into GetFeature requests (for read) and
13027      *     Transactions (for all others), this method does not make use of the
13028      *     format's read method (that is only about reading transaction
13029      *     responses).
13030      *
13031      * Parameters:
13032      * options - {Object} Options for the read operation, in addition to the
13033      *     options set on the instance (options set here will take precedence).
13034      *
13035      * To use a configured protocol to get e.g. a WFS hit count, applications
13036      * could do the following:
13037      *
13038      * (code)
13039      * protocol.read({
13040      *     readOptions: {output: "object"},
13041      *     resultType: "hits",
13042      *     maxFeatures: null,
13043      *     callback: function(resp) {
13044      *         // process resp.numberOfFeatures here
13045      *     }
13046      * });
13047      * (end)
13048      *
13049      * To use a configured protocol to use WFS paging (if supported by the
13050      * server), applications could do the following:
13051      *
13052      * (code)
13053      * protocol.read({
13054      *     startIndex: 0,
13055      *     count: 50
13056      * });
13057      * (end)
13058      *
13059      * To limit the attributes returned by the GetFeature request, applications
13060      * can use the propertyNames option to specify the properties to include in
13061      * the response:
13062      *
13063      * (code)
13064      * protocol.read({
13065      *     propertyNames: ["DURATION", "INTENSITY"]
13066      * });
13067      * (end)
13068      */
13069     read: function(options) {
13070         OpenLayers.Protocol.prototype.read.apply(this, arguments);
13071         options = OpenLayers.Util.extend({}, options);
13072         OpenLayers.Util.applyDefaults(options, this.options || {});
13073         var response = new OpenLayers.Protocol.Response({requestType: "read"});
13074         
13075         var data = OpenLayers.Format.XML.prototype.write.apply(
13076             this.format, [this.format.writeNode("wfs:GetFeature", options)]
13077         );
13078
13079         response.priv = OpenLayers.Request.POST({
13080             url: options.url,
13081             callback: this.createCallback(this.handleRead, response, options),
13082             params: options.params,
13083             headers: options.headers,
13084             data: data
13085         });
13086
13087         return response;
13088     },
13089
13090     /**
13091      * APIMethod: setFeatureType
13092      * Change the feature type on the fly.
13093      *
13094      * Parameters:
13095      * featureType - {String} Local (without prefix) feature typeName.
13096      */
13097     setFeatureType: function(featureType) {
13098         this.featureType = featureType;
13099         this.format.featureType = featureType;
13100     },
13101  
13102     /**
13103      * APIMethod: setGeometryName
13104      * Sets the geometryName option after instantiation.
13105      *
13106      * Parameters:
13107      * geometryName - {String} Name of geometry attribute.
13108      */
13109     setGeometryName: function(geometryName) {
13110         this.geometryName = geometryName;
13111         this.format.geometryName = geometryName;
13112     },
13113     
13114     /**
13115      * Method: handleRead
13116      * Deal with response from the read request.
13117      *
13118      * Parameters:
13119      * response - {<OpenLayers.Protocol.Response>} The response object to pass
13120      *     to the user callback.
13121      * options - {Object} The user options passed to the read call.
13122      */
13123     handleRead: function(response, options) {
13124         options = OpenLayers.Util.extend({}, options);
13125         OpenLayers.Util.applyDefaults(options, this.options);
13126
13127         if(options.callback) {
13128             var request = response.priv;
13129             if(request.status >= 200 && request.status < 300) {
13130                 // success
13131                 var result = this.parseResponse(request, options.readOptions);
13132                 if (result && result.success !== false) { 
13133                     if (options.readOptions && options.readOptions.output == "object") {
13134                         OpenLayers.Util.extend(response, result);
13135                     } else {
13136                         response.features = result;
13137                     }
13138                     response.code = OpenLayers.Protocol.Response.SUCCESS;
13139                 } else {
13140                     // failure (service exception)
13141                     response.code = OpenLayers.Protocol.Response.FAILURE;
13142                     response.error = result;
13143                 }
13144             } else {
13145                 // failure
13146                 response.code = OpenLayers.Protocol.Response.FAILURE;
13147             }
13148             options.callback.call(options.scope, response);
13149         }
13150     },
13151
13152     /**
13153      * Method: parseResponse
13154      * Read HTTP response body and return features
13155      *
13156      * Parameters:
13157      * request - {XMLHttpRequest} The request object
13158      * options - {Object} Optional object to pass to format's read
13159      *
13160      * Returns:
13161      * {Object} or {Array({<OpenLayers.Feature.Vector>})} or
13162      *     {<OpenLayers.Feature.Vector>} 
13163      * An object with a features property, an array of features or a single 
13164      * feature.
13165      */
13166     parseResponse: function(request, options) {
13167         var doc = request.responseXML;
13168         if(!doc || !doc.documentElement) {
13169             doc = request.responseText;
13170         }
13171         if(!doc || doc.length <= 0) {
13172             return null;
13173         }
13174         var result = (this.readFormat !== null) ? this.readFormat.read(doc) : 
13175             this.format.read(doc, options);
13176         if (!this.featureNS) {
13177             var format = this.readFormat || this.format;
13178             this.featureNS = format.featureNS;
13179             // no need to auto-configure again on subsequent reads
13180             format.autoConfig = false;
13181             if (!this.geometryName) {
13182                 this.setGeometryName(format.geometryName);
13183             }
13184         }
13185         return result;
13186     },
13187
13188     /**
13189      * Method: commit
13190      * Given a list of feature, assemble a batch request for update, create,
13191      *     and delete transactions.  A commit call on the prototype amounts
13192      *     to writing a WFS transaction - so the write method on the format
13193      *     is used.
13194      *
13195      * Parameters:
13196      * features - {Array(<OpenLayers.Feature.Vector>)}
13197      * options - {Object}
13198      *
13199      * Valid options properties:
13200      * nativeElements - {Array({Object})} Array of objects with information for writing
13201      * out <Native> elements, these objects have vendorId, safeToIgnore and
13202      * value properties. The <Native> element is intended to allow access to 
13203      * vendor specific capabilities of any particular web feature server or 
13204      * datastore.
13205      *
13206      * Returns:
13207      * {<OpenLayers.Protocol.Response>} A response object with a features
13208      *     property containing any insertIds and a priv property referencing
13209      *     the XMLHttpRequest object.
13210      */
13211     commit: function(features, options) {
13212
13213         options = OpenLayers.Util.extend({}, options);
13214         OpenLayers.Util.applyDefaults(options, this.options);
13215         
13216         var response = new OpenLayers.Protocol.Response({
13217             requestType: "commit",
13218             reqFeatures: features
13219         });
13220         response.priv = OpenLayers.Request.POST({
13221             url: options.url,
13222             headers: options.headers,
13223             data: this.format.write(features, options),
13224             callback: this.createCallback(this.handleCommit, response, options)
13225         });
13226         
13227         return response;
13228     },
13229     
13230     /**
13231      * Method: handleCommit
13232      * Called when the commit request returns.
13233      * 
13234      * Parameters:
13235      * response - {<OpenLayers.Protocol.Response>} The response object to pass
13236      *     to the user callback.
13237      * options - {Object} The user options passed to the commit call.
13238      */
13239     handleCommit: function(response, options) {
13240         if(options.callback) {
13241             var request = response.priv;
13242
13243             // ensure that we have an xml doc
13244             var data = request.responseXML;
13245             if(!data || !data.documentElement) {
13246                 data = request.responseText;
13247             }
13248             
13249             var obj = this.format.read(data) || {};
13250             
13251             response.insertIds = obj.insertIds || [];
13252             if (obj.success) {
13253                 response.code = OpenLayers.Protocol.Response.SUCCESS;
13254             } else {
13255                 response.code = OpenLayers.Protocol.Response.FAILURE;
13256                 response.error = obj;
13257             }
13258             options.callback.call(options.scope, response);
13259         }
13260     },
13261     
13262     /**
13263      * Method: filterDelete
13264      * Send a request that deletes all features by their filter.
13265      * 
13266      * Parameters:
13267      * filter - {<OpenLayers.Filter>} filter
13268      */
13269     filterDelete: function(filter, options) {
13270         options = OpenLayers.Util.extend({}, options);
13271         OpenLayers.Util.applyDefaults(options, this.options);    
13272         
13273         var response = new OpenLayers.Protocol.Response({
13274             requestType: "commit"
13275         });    
13276         
13277         var root = this.format.createElementNSPlus("wfs:Transaction", {
13278             attributes: {
13279                 service: "WFS",
13280                 version: this.version
13281             }
13282         });
13283         
13284         var deleteNode = this.format.createElementNSPlus("wfs:Delete", {
13285             attributes: {
13286                 typeName: (options.featureNS ? this.featurePrefix + ":" : "") +
13287                     options.featureType
13288             }
13289         });       
13290         
13291         if(options.featureNS) {
13292             deleteNode.setAttribute("xmlns:" + this.featurePrefix, options.featureNS);
13293         }
13294         var filterNode = this.format.writeNode("ogc:Filter", filter);
13295         
13296         deleteNode.appendChild(filterNode);
13297         
13298         root.appendChild(deleteNode);
13299         
13300         var data = OpenLayers.Format.XML.prototype.write.apply(
13301             this.format, [root]
13302         );
13303         
13304         return OpenLayers.Request.POST({
13305             url: this.url,
13306             callback : options.callback || function(){},
13307             data: data
13308         });   
13309         
13310     },
13311
13312     /**
13313      * Method: abort
13314      * Abort an ongoing request, the response object passed to
13315      * this method must come from this protocol (as a result
13316      * of a read, or commit operation).
13317      *
13318      * Parameters:
13319      * response - {<OpenLayers.Protocol.Response>}
13320      */
13321     abort: function(response) {
13322         if (response) {
13323             response.priv.abort();
13324         }
13325     },
13326   
13327     CLASS_NAME: "OpenLayers.Protocol.WFS.v1" 
13328 });
13329 /* ======================================================================
13330     OpenLayers/Format.js
13331    ====================================================================== */
13332
13333 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
13334  * full list of contributors). Published under the 2-clause BSD license.
13335  * See license.txt in the OpenLayers distribution or repository for the
13336  * full text of the license. */
13337
13338 /**
13339  * @requires OpenLayers/BaseTypes/Class.js
13340  * @requires OpenLayers/Util.js
13341  */
13342
13343 /**
13344  * Class: OpenLayers.Format
13345  * Base class for format reading/writing a variety of formats.  Subclasses
13346  *     of OpenLayers.Format are expected to have read and write methods.
13347  */
13348 OpenLayers.Format = OpenLayers.Class({
13349     
13350     /**
13351      * Property: options
13352      * {Object} A reference to options passed to the constructor.
13353      */
13354     options: null,
13355     
13356     /**
13357      * APIProperty: externalProjection
13358      * {<OpenLayers.Projection>} When passed a externalProjection and
13359      *     internalProjection, the format will reproject the geometries it
13360      *     reads or writes. The externalProjection is the projection used by
13361      *     the content which is passed into read or which comes out of write.
13362      *     In order to reproject, a projection transformation function for the
13363      *     specified projections must be available. This support may be 
13364      *     provided via proj4js or via a custom transformation function. See
13365      *     {<OpenLayers.Projection.addTransform>} for more information on
13366      *     custom transformations.
13367      */
13368     externalProjection: null,
13369
13370     /**
13371      * APIProperty: internalProjection
13372      * {<OpenLayers.Projection>} When passed a externalProjection and
13373      *     internalProjection, the format will reproject the geometries it
13374      *     reads or writes. The internalProjection is the projection used by
13375      *     the geometries which are returned by read or which are passed into
13376      *     write.  In order to reproject, a projection transformation function
13377      *     for the specified projections must be available. This support may be
13378      *     provided via proj4js or via a custom transformation function. See
13379      *     {<OpenLayers.Projection.addTransform>} for more information on
13380      *     custom transformations.
13381      */
13382     internalProjection: null,
13383
13384     /**
13385      * APIProperty: data
13386      * {Object} When <keepData> is true, this is the parsed string sent to
13387      *     <read>.
13388      */
13389     data: null,
13390
13391     /**
13392      * APIProperty: keepData
13393      * {Object} Maintain a reference (<data>) to the most recently read data.
13394      *     Default is false.
13395      */
13396     keepData: false,
13397
13398     /**
13399      * Constructor: OpenLayers.Format
13400      * Instances of this class are not useful.  See one of the subclasses.
13401      *
13402      * Parameters:
13403      * options - {Object} An optional object with properties to set on the
13404      *           format
13405      *
13406      * Valid options:
13407      * keepData - {Boolean} If true, upon <read>, the data property will be
13408      *     set to the parsed object (e.g. the json or xml object).
13409      *
13410      * Returns:
13411      * An instance of OpenLayers.Format
13412      */
13413     initialize: function(options) {
13414         OpenLayers.Util.extend(this, options);
13415         this.options = options;
13416     },
13417     
13418     /**
13419      * APIMethod: destroy
13420      * Clean up.
13421      */
13422     destroy: function() {
13423     },
13424
13425     /**
13426      * Method: read
13427      * Read data from a string, and return an object whose type depends on the
13428      * subclass. 
13429      * 
13430      * Parameters:
13431      * data - {string} Data to read/parse.
13432      *
13433      * Returns:
13434      * Depends on the subclass
13435      */
13436     read: function(data) {
13437         throw new Error('Read not implemented.');
13438     },
13439     
13440     /**
13441      * Method: write
13442      * Accept an object, and return a string. 
13443      *
13444      * Parameters:
13445      * object - {Object} Object to be serialized
13446      *
13447      * Returns:
13448      * {String} A string representation of the object.
13449      */
13450     write: function(object) {
13451         throw new Error('Write not implemented.');
13452     },
13453
13454     CLASS_NAME: "OpenLayers.Format"
13455 });     
13456 /* ======================================================================
13457     OpenLayers/Format/XML.js
13458    ====================================================================== */
13459
13460 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
13461  * full list of contributors). Published under the 2-clause BSD license.
13462  * See license.txt in the OpenLayers distribution or repository for the
13463  * full text of the license. */
13464
13465 /**
13466  * @requires OpenLayers/Format.js
13467  */
13468
13469 /**
13470  * Class: OpenLayers.Format.XML
13471  * Read and write XML.  For cross-browser XML generation, use methods on an
13472  *     instance of the XML format class instead of on <code>document<end>.
13473  *     The DOM creation and traversing methods exposed here all mimic the
13474  *     W3C XML DOM methods.  Create a new parser with the
13475  *     <OpenLayers.Format.XML> constructor.
13476  *
13477  * Inherits from:
13478  *  - <OpenLayers.Format>
13479  */
13480 OpenLayers.Format.XML = OpenLayers.Class(OpenLayers.Format, {
13481     
13482     /**
13483      * Property: namespaces
13484      * {Object} Mapping of namespace aliases to namespace URIs.  Properties
13485      *     of this object should not be set individually.  Read-only.  All
13486      *     XML subclasses should have their own namespaces object.  Use
13487      *     <setNamespace> to add or set a namespace alias after construction.
13488      */
13489     namespaces: null,
13490     
13491     /**
13492      * Property: namespaceAlias
13493      * {Object} Mapping of namespace URI to namespace alias.  This object
13494      *     is read-only.  Use <setNamespace> to add or set a namespace alias.
13495      */
13496     namespaceAlias: null,
13497     
13498     /**
13499      * Property: defaultPrefix
13500      * {String} The default namespace alias for creating element nodes.
13501      */
13502     defaultPrefix: null,
13503     
13504     /**
13505      * Property: readers
13506      * Contains public functions, grouped by namespace prefix, that will
13507      *     be applied when a namespaced node is found matching the function
13508      *     name.  The function will be applied in the scope of this parser
13509      *     with two arguments: the node being read and a context object passed
13510      *     from the parent.
13511      */
13512     readers: {},
13513     
13514     /**
13515      * Property: writers
13516      * As a compliment to the <readers> property, this structure contains public
13517      *     writing functions grouped by namespace alias and named like the
13518      *     node names they produce.
13519      */
13520     writers: {},
13521
13522     /**
13523      * Property: xmldom
13524      * {XMLDom} If this browser uses ActiveX, this will be set to a XMLDOM
13525      *     object.  It is not intended to be a browser sniffing property.
13526      *     Instead, the xmldom property is used instead of <code>document<end>
13527      *     where namespaced node creation methods are not supported. In all
13528      *     other browsers, this remains null.
13529      */
13530     xmldom: null,
13531
13532     /**
13533      * Constructor: OpenLayers.Format.XML
13534      * Construct an XML parser.  The parser is used to read and write XML.
13535      *     Reading XML from a string returns a DOM element.  Writing XML from
13536      *     a DOM element returns a string.
13537      *
13538      * Parameters:
13539      * options - {Object} Optional object whose properties will be set on
13540      *     the object.
13541      */
13542     initialize: function(options) {
13543         if (OpenLayers.Format.XML.supportActiveX) {
13544             this.xmldom = new ActiveXObject("Microsoft.XMLDOM");
13545         }
13546         OpenLayers.Format.prototype.initialize.apply(this, [options]);
13547         // clone the namespace object and set all namespace aliases
13548         this.namespaces = OpenLayers.Util.extend({}, this.namespaces);
13549         this.namespaceAlias = {};
13550         for(var alias in this.namespaces) {
13551             this.namespaceAlias[this.namespaces[alias]] = alias;
13552         }
13553     },
13554     
13555     /**
13556      * APIMethod: destroy
13557      * Clean up.
13558      */
13559     destroy: function() {
13560         this.xmldom = null;
13561         OpenLayers.Format.prototype.destroy.apply(this, arguments);
13562     },
13563     
13564     /**
13565      * Method: setNamespace
13566      * Set a namespace alias and URI for the format.
13567      *
13568      * Parameters:
13569      * alias - {String} The namespace alias (prefix).
13570      * uri - {String} The namespace URI.
13571      */
13572     setNamespace: function(alias, uri) {
13573         this.namespaces[alias] = uri;
13574         this.namespaceAlias[uri] = alias;
13575     },
13576
13577     /**
13578      * APIMethod: read
13579      * Deserialize a XML string and return a DOM node.
13580      *
13581      * Parameters:
13582      * text - {String} A XML string
13583      
13584      * Returns:
13585      * {DOMElement} A DOM node
13586      */
13587     read: function(text) {
13588         var index = text.indexOf('<');
13589         if(index > 0) {
13590             text = text.substring(index);
13591         }
13592         var node = OpenLayers.Util.Try(
13593             OpenLayers.Function.bind((
13594                 function() {
13595                     var xmldom;
13596                     /**
13597                      * Since we want to be able to call this method on the prototype
13598                      * itself, this.xmldom may not exist even if in IE.
13599                      */
13600                     if (OpenLayers.Format.XML.supportActiveX && !this.xmldom) {
13601                         xmldom = new ActiveXObject("Microsoft.XMLDOM");
13602                     } else {
13603                         xmldom = this.xmldom;
13604                         
13605                     }
13606                     xmldom.loadXML(text);
13607                     return xmldom;
13608                 }
13609             ), this),
13610             function() {
13611                 return new DOMParser().parseFromString(text, 'text/xml');
13612             },
13613             function() {
13614                 var req = new XMLHttpRequest();
13615                 req.open("GET", "data:" + "text/xml" +
13616                          ";charset=utf-8," + encodeURIComponent(text), false);
13617                 if(req.overrideMimeType) {
13618                     req.overrideMimeType("text/xml");
13619                 }
13620                 req.send(null);
13621                 return req.responseXML;
13622             }
13623         );
13624
13625         if(this.keepData) {
13626             this.data = node;
13627         }
13628
13629         return node;
13630     },
13631
13632     /**
13633      * APIMethod: write
13634      * Serialize a DOM node into a XML string.
13635      * 
13636      * Parameters:
13637      * node - {DOMElement} A DOM node.
13638      *
13639      * Returns:
13640      * {String} The XML string representation of the input node.
13641      */
13642     write: function(node) {
13643         var data;
13644         if(this.xmldom) {
13645             data = node.xml;
13646         } else {
13647             var serializer = new XMLSerializer();
13648             if (node.nodeType == 1) {
13649                 // Add nodes to a document before serializing. Everything else
13650                 // is serialized as is. This may need more work. See #1218 .
13651                 var doc = document.implementation.createDocument("", "", null);
13652                 if (doc.importNode) {
13653                     node = doc.importNode(node, true);
13654                 }
13655                 doc.appendChild(node);
13656                 data = serializer.serializeToString(doc);
13657             } else {
13658                 data = serializer.serializeToString(node);
13659             }
13660         }
13661         return data;
13662     },
13663
13664     /**
13665      * APIMethod: createElementNS
13666      * Create a new element with namespace.  This node can be appended to
13667      *     another node with the standard node.appendChild method.  For
13668      *     cross-browser support, this method must be used instead of
13669      *     document.createElementNS.
13670      *
13671      * Parameters:
13672      * uri - {String} Namespace URI for the element.
13673      * name - {String} The qualified name of the element (prefix:localname).
13674      * 
13675      * Returns:
13676      * {Element} A DOM element with namespace.
13677      */
13678     createElementNS: function(uri, name) {
13679         var element;
13680         if(this.xmldom) {
13681             if(typeof uri == "string") {
13682                 element = this.xmldom.createNode(1, name, uri);
13683             } else {
13684                 element = this.xmldom.createNode(1, name, "");
13685             }
13686         } else {
13687             element = document.createElementNS(uri, name);
13688         }
13689         return element;
13690     },
13691
13692     /**
13693      * APIMethod: createDocumentFragment
13694      * Create a document fragment node that can be appended to another node
13695      *     created by createElementNS.  This will call 
13696      *     document.createDocumentFragment outside of IE.  In IE, the ActiveX
13697      *     object's createDocumentFragment method is used.
13698      *
13699      * Returns:
13700      * {Element} A document fragment.
13701      */
13702     createDocumentFragment: function() {
13703         var element;
13704         if (this.xmldom) {
13705             element = this.xmldom.createDocumentFragment();
13706         } else {
13707             element = document.createDocumentFragment();
13708         }
13709         return element;
13710     },
13711
13712     /**
13713      * APIMethod: createTextNode
13714      * Create a text node.  This node can be appended to another node with
13715      *     the standard node.appendChild method.  For cross-browser support,
13716      *     this method must be used instead of document.createTextNode.
13717      * 
13718      * Parameters:
13719      * text - {String} The text of the node.
13720      * 
13721      * Returns: 
13722      * {DOMElement} A DOM text node.
13723      */
13724     createTextNode: function(text) {
13725         var node;
13726         if (typeof text !== "string") {
13727             text = String(text);
13728         }
13729         if(this.xmldom) {
13730             node = this.xmldom.createTextNode(text);
13731         } else {
13732             node = document.createTextNode(text);
13733         }
13734         return node;
13735     },
13736
13737     /**
13738      * APIMethod: getElementsByTagNameNS
13739      * Get a list of elements on a node given the namespace URI and local name.
13740      *     To return all nodes in a given namespace, use '*' for the name
13741      *     argument.  To return all nodes of a given (local) name, regardless
13742      *     of namespace, use '*' for the uri argument.
13743      * 
13744      * Parameters:
13745      * node - {Element} Node on which to search for other nodes.
13746      * uri - {String} Namespace URI.
13747      * name - {String} Local name of the tag (without the prefix).
13748      * 
13749      * Returns:
13750      * {NodeList} A node list or array of elements.
13751      */
13752     getElementsByTagNameNS: function(node, uri, name) {
13753         var elements = [];
13754         if(node.getElementsByTagNameNS) {
13755             elements = node.getElementsByTagNameNS(uri, name);
13756         } else {
13757             // brute force method
13758             var allNodes = node.getElementsByTagName("*");
13759             var potentialNode, fullName;
13760             for(var i=0, len=allNodes.length; i<len; ++i) {
13761                 potentialNode = allNodes[i];
13762                 fullName = (potentialNode.prefix) ?
13763                            (potentialNode.prefix + ":" + name) : name;
13764                 if((name == "*") || (fullName == potentialNode.nodeName)) {
13765                     if((uri == "*") || (uri == potentialNode.namespaceURI)) {
13766                         elements.push(potentialNode);
13767                     }
13768                 }
13769             }
13770         }
13771         return elements;
13772     },
13773
13774     /**
13775      * APIMethod: getAttributeNodeNS
13776      * Get an attribute node given the namespace URI and local name.
13777      * 
13778      * Parameters:
13779      * node - {Element} Node on which to search for attribute nodes.
13780      * uri - {String} Namespace URI.
13781      * name - {String} Local name of the attribute (without the prefix).
13782      * 
13783      * Returns:
13784      * {DOMElement} An attribute node or null if none found.
13785      */
13786     getAttributeNodeNS: function(node, uri, name) {
13787         var attributeNode = null;
13788         if(node.getAttributeNodeNS) {
13789             attributeNode = node.getAttributeNodeNS(uri, name);
13790         } else {
13791             var attributes = node.attributes;
13792             var potentialNode, fullName;
13793             for(var i=0, len=attributes.length; i<len; ++i) {
13794                 potentialNode = attributes[i];
13795                 if(potentialNode.namespaceURI == uri) {
13796                     fullName = (potentialNode.prefix) ?
13797                                (potentialNode.prefix + ":" + name) : name;
13798                     if(fullName == potentialNode.nodeName) {
13799                         attributeNode = potentialNode;
13800                         break;
13801                     }
13802                 }
13803             }
13804         }
13805         return attributeNode;
13806     },
13807
13808     /**
13809      * APIMethod: getAttributeNS
13810      * Get an attribute value given the namespace URI and local name.
13811      * 
13812      * Parameters:
13813      * node - {Element} Node on which to search for an attribute.
13814      * uri - {String} Namespace URI.
13815      * name - {String} Local name of the attribute (without the prefix).
13816      * 
13817      * Returns:
13818      * {String} An attribute value or and empty string if none found.
13819      */
13820     getAttributeNS: function(node, uri, name) {
13821         var attributeValue = "";
13822         if(node.getAttributeNS) {
13823             attributeValue = node.getAttributeNS(uri, name) || "";
13824         } else {
13825             var attributeNode = this.getAttributeNodeNS(node, uri, name);
13826             if(attributeNode) {
13827                 attributeValue = attributeNode.nodeValue;
13828             }
13829         }
13830         return attributeValue;
13831     },
13832     
13833     /**
13834      * APIMethod: getChildValue
13835      * Get the textual value of the node if it exists, or return an
13836      *     optional default string.  Returns an empty string if no first child
13837      *     exists and no default value is supplied.
13838      *
13839      * Parameters:
13840      * node - {DOMElement} The element used to look for a first child value.
13841      * def - {String} Optional string to return in the event that no
13842      *     first child value exists.
13843      *
13844      * Returns:
13845      * {String} The value of the first child of the given node.
13846      */
13847     getChildValue: function(node, def) {
13848         var value = def || "";
13849         if(node) {
13850             for(var child=node.firstChild; child; child=child.nextSibling) {
13851                 switch(child.nodeType) {
13852                     case 3: // text node
13853                     case 4: // cdata section
13854                         value += child.nodeValue;
13855                 }
13856             }
13857         }
13858         return value;
13859     },
13860
13861     /**
13862      * APIMethod: isSimpleContent
13863      * Test if the given node has only simple content (i.e. no child element
13864      *     nodes).
13865      *
13866      * Parameters:
13867      * node - {DOMElement} An element node.
13868      *
13869      * Returns:
13870      * {Boolean} The node has no child element nodes (nodes of type 1). 
13871      */
13872     isSimpleContent: function(node) {
13873         var simple = true;
13874         for(var child=node.firstChild; child; child=child.nextSibling) {
13875             if(child.nodeType === 1) {
13876                 simple = false;
13877                 break;
13878             }
13879         }
13880         return simple;
13881     },
13882     
13883     /**
13884      * APIMethod: contentType
13885      * Determine the content type for a given node.
13886      *
13887      * Parameters:
13888      * node - {DOMElement}
13889      *
13890      * Returns:
13891      * {Integer} One of OpenLayers.Format.XML.CONTENT_TYPE.{EMPTY,SIMPLE,COMPLEX,MIXED}
13892      *     if the node has no, simple, complex, or mixed content.
13893      */
13894     contentType: function(node) {
13895         var simple = false,
13896             complex = false;
13897             
13898         var type = OpenLayers.Format.XML.CONTENT_TYPE.EMPTY;
13899
13900         for(var child=node.firstChild; child; child=child.nextSibling) {
13901             switch(child.nodeType) {
13902                 case 1: // element
13903                     complex = true;
13904                     break;
13905                 case 8: // comment
13906                     break;
13907                 default:
13908                     simple = true;
13909             }
13910             if(complex && simple) {
13911                 break;
13912             }
13913         }
13914         
13915         if(complex && simple) {
13916             type = OpenLayers.Format.XML.CONTENT_TYPE.MIXED;
13917         } else if(complex) {
13918             return OpenLayers.Format.XML.CONTENT_TYPE.COMPLEX;
13919         } else if(simple) {
13920             return OpenLayers.Format.XML.CONTENT_TYPE.SIMPLE;
13921         }
13922         return type;
13923     },
13924
13925     /**
13926      * APIMethod: hasAttributeNS
13927      * Determine whether a node has a particular attribute matching the given
13928      *     name and namespace.
13929      * 
13930      * Parameters:
13931      * node - {Element} Node on which to search for an attribute.
13932      * uri - {String} Namespace URI.
13933      * name - {String} Local name of the attribute (without the prefix).
13934      * 
13935      * Returns:
13936      * {Boolean} The node has an attribute matching the name and namespace.
13937      */
13938     hasAttributeNS: function(node, uri, name) {
13939         var found = false;
13940         if(node.hasAttributeNS) {
13941             found = node.hasAttributeNS(uri, name);
13942         } else {
13943             found = !!this.getAttributeNodeNS(node, uri, name);
13944         }
13945         return found;
13946     },
13947     
13948     /**
13949      * APIMethod: setAttributeNS
13950      * Adds a new attribute or changes the value of an attribute with the given
13951      *     namespace and name.
13952      *
13953      * Parameters:
13954      * node - {Element} Element node on which to set the attribute.
13955      * uri - {String} Namespace URI for the attribute.
13956      * name - {String} Qualified name (prefix:localname) for the attribute.
13957      * value - {String} Attribute value.
13958      */
13959     setAttributeNS: function(node, uri, name, value) {
13960         if(node.setAttributeNS) {
13961             node.setAttributeNS(uri, name, value);
13962         } else {
13963             if(this.xmldom) {
13964                 if(uri) {
13965                     var attribute = node.ownerDocument.createNode(
13966                         2, name, uri
13967                     );
13968                     attribute.nodeValue = value;
13969                     node.setAttributeNode(attribute);
13970                 } else {
13971                     node.setAttribute(name, value);
13972                 }
13973             } else {
13974                 throw "setAttributeNS not implemented";
13975             }
13976         }
13977     },
13978
13979     /**
13980      * Method: createElementNSPlus
13981      * Shorthand for creating namespaced elements with optional attributes and
13982      *     child text nodes.
13983      *
13984      * Parameters:
13985      * name - {String} The qualified node name.
13986      * options - {Object} Optional object for node configuration.
13987      *
13988      * Valid options:
13989      * uri - {String} Optional namespace uri for the element - supply a prefix
13990      *     instead if the namespace uri is a property of the format's namespace
13991      *     object.
13992      * attributes - {Object} Optional attributes to be set using the
13993      *     <setAttributes> method.
13994      * value - {String} Optional text to be appended as a text node.
13995      *
13996      * Returns:
13997      * {Element} An element node.
13998      */
13999     createElementNSPlus: function(name, options) {
14000         options = options || {};
14001         // order of prefix preference
14002         // 1. in the uri option
14003         // 2. in the prefix option
14004         // 3. in the qualified name
14005         // 4. from the defaultPrefix
14006         var uri = options.uri || this.namespaces[options.prefix];
14007         if(!uri) {
14008             var loc = name.indexOf(":");
14009             uri = this.namespaces[name.substring(0, loc)];
14010         }
14011         if(!uri) {
14012             uri = this.namespaces[this.defaultPrefix];
14013         }
14014         var node = this.createElementNS(uri, name);
14015         if(options.attributes) {
14016             this.setAttributes(node, options.attributes);
14017         }
14018         var value = options.value;
14019         if(value != null) {
14020             node.appendChild(this.createTextNode(value));
14021         }
14022         return node;
14023     },
14024     
14025     /**
14026      * Method: setAttributes
14027      * Set multiple attributes given key value pairs from an object.
14028      *
14029      * Parameters:
14030      * node - {Element} An element node.
14031      * obj - {Object || Array} An object whose properties represent attribute
14032      *     names and values represent attribute values.  If an attribute name
14033      *     is a qualified name ("prefix:local"), the prefix will be looked up
14034      *     in the parsers {namespaces} object.  If the prefix is found,
14035      *     setAttributeNS will be used instead of setAttribute.
14036      */
14037     setAttributes: function(node, obj) {
14038         var value, uri;
14039         for(var name in obj) {
14040             if(obj[name] != null && obj[name].toString) {
14041                 value = obj[name].toString();
14042                 // check for qualified attribute name ("prefix:local")
14043                 uri = this.namespaces[name.substring(0, name.indexOf(":"))] || null;
14044                 this.setAttributeNS(node, uri, name, value);
14045             }
14046         }
14047     },
14048
14049     /**
14050      * Method: getFirstElementChild
14051      * Implementation of firstElementChild attribute that works on ie7 and ie8.
14052      *
14053      * Parameters:
14054      * node - {DOMElement} The parent node (required).
14055      *
14056      * Returns:
14057      * {DOMElement} The first child element.
14058      */
14059     getFirstElementChild: function(node) {
14060         if (node.firstElementChild) {
14061             return node.firstElementChild;
14062         }
14063         else {
14064             var child = node.firstChild;
14065             while (child.nodeType != 1 && (child = child.nextSibling)) {}
14066             return child;
14067         }
14068     },
14069
14070     /**
14071      * Method: readNode
14072      * Shorthand for applying one of the named readers given the node
14073      *     namespace and local name.  Readers take two args (node, obj) and
14074      *     generally extend or modify the second.
14075      *
14076      * Parameters:
14077      * node - {DOMElement} The node to be read (required).
14078      * obj - {Object} The object to be modified (optional).
14079      *
14080      * Returns:
14081      * {Object} The input object, modified (or a new one if none was provided).
14082      */
14083     readNode: function(node, obj) {
14084         if(!obj) {
14085             obj = {};
14086         }
14087         var group = this.readers[node.namespaceURI ? this.namespaceAlias[node.namespaceURI]: this.defaultPrefix];
14088         if(group) {
14089             var local = node.localName || node.nodeName.split(":").pop();
14090             var reader = group[local] || group["*"];
14091             if(reader) {
14092                 reader.apply(this, [node, obj]);
14093             }
14094         }
14095         return obj;
14096     },
14097
14098     /**
14099      * Method: readChildNodes
14100      * Shorthand for applying the named readers to all children of a node.
14101      *     For each child of type 1 (element), <readSelf> is called.
14102      *
14103      * Parameters:
14104      * node - {DOMElement} The node to be read (required).
14105      * obj - {Object} The object to be modified (optional).
14106      *
14107      * Returns:
14108      * {Object} The input object, modified.
14109      */
14110     readChildNodes: function(node, obj) {
14111         if(!obj) {
14112             obj = {};
14113         }
14114         var children = node.childNodes;
14115         var child;
14116         for(var i=0, len=children.length; i<len; ++i) {
14117             child = children[i];
14118             if(child.nodeType == 1) {
14119                 this.readNode(child, obj);
14120             }
14121         }
14122         return obj;
14123     },
14124
14125     /**
14126      * Method: writeNode
14127      * Shorthand for applying one of the named writers and appending the
14128      *     results to a node.  If a qualified name is not provided for the
14129      *     second argument (and a local name is used instead), the namespace
14130      *     of the parent node will be assumed.
14131      *
14132      * Parameters:
14133      * name - {String} The name of a node to generate.  If a qualified name
14134      *     (e.g. "pre:Name") is used, the namespace prefix is assumed to be
14135      *     in the <writers> group.  If a local name is used (e.g. "Name") then
14136      *     the namespace of the parent is assumed.  If a local name is used
14137      *     and no parent is supplied, then the default namespace is assumed.
14138      * obj - {Object} Structure containing data for the writer.
14139      * parent - {DOMElement} Result will be appended to this node.  If no parent
14140      *     is supplied, the node will not be appended to anything.
14141      *
14142      * Returns:
14143      * {DOMElement} The child node.
14144      */
14145     writeNode: function(name, obj, parent) {
14146         var prefix, local;
14147         var split = name.indexOf(":");
14148         if(split > 0) {
14149             prefix = name.substring(0, split);
14150             local = name.substring(split + 1);
14151         } else {
14152             if(parent) {
14153                 prefix = this.namespaceAlias[parent.namespaceURI];
14154             } else {
14155                 prefix = this.defaultPrefix;
14156             }
14157             local = name;
14158         }
14159         var child = this.writers[prefix][local].apply(this, [obj]);
14160         if(parent) {
14161             parent.appendChild(child);
14162         }
14163         return child;
14164     },
14165
14166     /**
14167      * APIMethod: getChildEl
14168      * Get the first child element.  Optionally only return the first child
14169      *     if it matches the given name and namespace URI.
14170      *
14171      * Parameters:
14172      * node - {DOMElement} The parent node.
14173      * name - {String} Optional node name (local) to search for.
14174      * uri - {String} Optional namespace URI to search for.
14175      *
14176      * Returns:
14177      * {DOMElement} The first child.  Returns null if no element is found, if
14178      *     something significant besides an element is found, or if the element
14179      *     found does not match the optional name and uri.
14180      */
14181     getChildEl: function(node, name, uri) {
14182         return node && this.getThisOrNextEl(node.firstChild, name, uri);
14183     },
14184     
14185     /**
14186      * APIMethod: getNextEl
14187      * Get the next sibling element.  Optionally get the first sibling only
14188      *     if it matches the given local name and namespace URI.
14189      *
14190      * Parameters:
14191      * node - {DOMElement} The node.
14192      * name - {String} Optional local name of the sibling to search for.
14193      * uri - {String} Optional namespace URI of the sibling to search for.
14194      *
14195      * Returns:
14196      * {DOMElement} The next sibling element.  Returns null if no element is
14197      *     found, something significant besides an element is found, or the
14198      *     found element does not match the optional name and uri.
14199      */
14200     getNextEl: function(node, name, uri) {
14201         return node && this.getThisOrNextEl(node.nextSibling, name, uri);
14202     },
14203     
14204     /**
14205      * Method: getThisOrNextEl
14206      * Return this node or the next element node.  Optionally get the first
14207      *     sibling with the given local name or namespace URI.
14208      *
14209      * Parameters:
14210      * node - {DOMElement} The node.
14211      * name - {String} Optional local name of the sibling to search for.
14212      * uri - {String} Optional namespace URI of the sibling to search for.
14213      *
14214      * Returns:
14215      * {DOMElement} The next sibling element.  Returns null if no element is
14216      *     found, something significant besides an element is found, or the
14217      *     found element does not match the query.
14218      */
14219     getThisOrNextEl: function(node, name, uri) {
14220         outer: for(var sibling=node; sibling; sibling=sibling.nextSibling) {
14221             switch(sibling.nodeType) {
14222                 case 1: // Element
14223                     if((!name || name === (sibling.localName || sibling.nodeName.split(":").pop())) &&
14224                        (!uri || uri === sibling.namespaceURI)) {
14225                         // matches
14226                         break outer;
14227                     }
14228                     sibling = null;
14229                     break outer;
14230                 case 3: // Text
14231                     if(/^\s*$/.test(sibling.nodeValue)) {
14232                         break;
14233                     }
14234                 case 4: // CDATA
14235                 case 6: // ENTITY_NODE
14236                 case 12: // NOTATION_NODE
14237                 case 10: // DOCUMENT_TYPE_NODE
14238                 case 11: // DOCUMENT_FRAGMENT_NODE
14239                     sibling = null;
14240                     break outer;
14241             } // ignore comments and processing instructions
14242         }
14243         return sibling || null;
14244     },
14245     
14246     /**
14247      * APIMethod: lookupNamespaceURI
14248      * Takes a prefix and returns the namespace URI associated with it on the given
14249      *     node if found (and null if not). Supplying null for the prefix will
14250      *     return the default namespace.
14251      *
14252      * For browsers that support it, this calls the native lookupNamesapceURI
14253      *     function.  In other browsers, this is an implementation of
14254      *     http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespaceURI.
14255      *
14256      * For browsers that don't support the attribute.ownerElement property, this
14257      *     method cannot be called on attribute nodes.
14258      *     
14259      * Parameters:
14260      * node - {DOMElement} The node from which to start looking.
14261      * prefix - {String} The prefix to lookup or null to lookup the default namespace.
14262      * 
14263      * Returns:
14264      * {String} The namespace URI for the given prefix.  Returns null if the prefix
14265      *     cannot be found or the node is the wrong type.
14266      */
14267     lookupNamespaceURI: function(node, prefix) {
14268         var uri = null;
14269         if(node) {
14270             if(node.lookupNamespaceURI) {
14271                 uri = node.lookupNamespaceURI(prefix);
14272             } else {
14273                 outer: switch(node.nodeType) {
14274                     case 1: // ELEMENT_NODE
14275                         if(node.namespaceURI !== null && node.prefix === prefix) {
14276                             uri = node.namespaceURI;
14277                             break outer;
14278                         }
14279                         var len = node.attributes.length;
14280                         if(len) {
14281                             var attr;
14282                             for(var i=0; i<len; ++i) {
14283                                 attr = node.attributes[i];
14284                                 if(attr.prefix === "xmlns" && attr.name === "xmlns:" + prefix) {
14285                                     uri = attr.value || null;
14286                                     break outer;
14287                                 } else if(attr.name === "xmlns" && prefix === null) {
14288                                     uri = attr.value || null;
14289                                     break outer;
14290                                 }
14291                             }
14292                         }
14293                         uri = this.lookupNamespaceURI(node.parentNode, prefix);
14294                         break outer;
14295                     case 2: // ATTRIBUTE_NODE
14296                         uri = this.lookupNamespaceURI(node.ownerElement, prefix);
14297                         break outer;
14298                     case 9: // DOCUMENT_NODE
14299                         uri = this.lookupNamespaceURI(node.documentElement, prefix);
14300                         break outer;
14301                     case 6: // ENTITY_NODE
14302                     case 12: // NOTATION_NODE
14303                     case 10: // DOCUMENT_TYPE_NODE
14304                     case 11: // DOCUMENT_FRAGMENT_NODE
14305                         break outer;
14306                     default: 
14307                         // TEXT_NODE (3), CDATA_SECTION_NODE (4), ENTITY_REFERENCE_NODE (5),
14308                         // PROCESSING_INSTRUCTION_NODE (7), COMMENT_NODE (8)
14309                         uri =  this.lookupNamespaceURI(node.parentNode, prefix);
14310                         break outer;
14311                 }
14312             }
14313         }
14314         return uri;
14315     },
14316     
14317     /**
14318      * Method: getXMLDoc
14319      * Get an XML document for nodes that are not supported in HTML (e.g.
14320      * createCDATASection). On IE, this will either return an existing or
14321      * create a new <xmldom> on the instance. On other browsers, this will
14322      * either return an existing or create a new shared document (see
14323      * <OpenLayers.Format.XML.document>).
14324      *
14325      * Returns:
14326      * {XMLDocument}
14327      */
14328     getXMLDoc: function() {
14329         if (!OpenLayers.Format.XML.document && !this.xmldom) {
14330             if (document.implementation && document.implementation.createDocument) {
14331                 OpenLayers.Format.XML.document =
14332                     document.implementation.createDocument("", "", null);
14333             } else if (!this.xmldom && OpenLayers.Format.XML.supportActiveX) {
14334                 this.xmldom = new ActiveXObject("Microsoft.XMLDOM");
14335             }
14336         }
14337         return OpenLayers.Format.XML.document || this.xmldom;
14338     },
14339
14340     CLASS_NAME: "OpenLayers.Format.XML" 
14341
14342 });     
14343
14344 OpenLayers.Format.XML.CONTENT_TYPE = {EMPTY: 0, SIMPLE: 1, COMPLEX: 2, MIXED: 3};
14345
14346 /**
14347  * APIFunction: OpenLayers.Format.XML.lookupNamespaceURI
14348  * Takes a prefix and returns the namespace URI associated with it on the given
14349  *     node if found (and null if not). Supplying null for the prefix will
14350  *     return the default namespace.
14351  *
14352  * For browsers that support it, this calls the native lookupNamesapceURI
14353  *     function.  In other browsers, this is an implementation of
14354  *     http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespaceURI.
14355  *
14356  * For browsers that don't support the attribute.ownerElement property, this
14357  *     method cannot be called on attribute nodes.
14358  *     
14359  * Parameters:
14360  * node - {DOMElement} The node from which to start looking.
14361  * prefix - {String} The prefix to lookup or null to lookup the default namespace.
14362  * 
14363  * Returns:
14364  * {String} The namespace URI for the given prefix.  Returns null if the prefix
14365  *     cannot be found or the node is the wrong type.
14366  */
14367 OpenLayers.Format.XML.lookupNamespaceURI = OpenLayers.Function.bind(
14368     OpenLayers.Format.XML.prototype.lookupNamespaceURI,
14369     OpenLayers.Format.XML.prototype
14370 );
14371
14372 /**
14373  * Property: OpenLayers.Format.XML.document
14374  * {XMLDocument} XML document to reuse for creating non-HTML compliant nodes,
14375  * like document.createCDATASection.
14376  */
14377 OpenLayers.Format.XML.document = null;
14378
14379 /**
14380  * APIFunction: OpenLayers.Format.XML.supportActiveX
14381  * Returns a poolean flag to check if this browser uses ActiveX.
14382  */
14383 OpenLayers.Format.XML.supportActiveX = (function () {
14384     return (Object.getOwnPropertyDescriptor &&
14385             Object.getOwnPropertyDescriptor(window, "ActiveXObject")) ||
14386             ("ActiveXObject" in window);
14387 })();
14388 /* ======================================================================
14389     OpenLayers/Format/WFST.js
14390    ====================================================================== */
14391
14392 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
14393  * full list of contributors). Published under the 2-clause BSD license.
14394  * See license.txt in the OpenLayers distribution or repository for the
14395  * full text of the license. */
14396
14397 /**
14398  * @requires OpenLayers/Format.js
14399  */
14400
14401 /**
14402  * Function: OpenLayers.Format.WFST
14403  * Used to create a versioned WFS protocol.  Default version is 1.0.0.
14404  *
14405  * Returns:
14406  * {<OpenLayers.Format>} A WFST format of the given version.
14407  */
14408 OpenLayers.Format.WFST = function(options) {
14409     options = OpenLayers.Util.applyDefaults(
14410         options, OpenLayers.Format.WFST.DEFAULTS
14411     );
14412     var cls = OpenLayers.Format.WFST["v"+options.version.replace(/\./g, "_")];
14413     if(!cls) {
14414         throw "Unsupported WFST version: " + options.version;
14415     }
14416     return new cls(options);
14417 };
14418
14419 /**
14420  * Constant: OpenLayers.Format.WFST.DEFAULTS
14421  * {Object} Default properties for the WFST format.
14422  */
14423 OpenLayers.Format.WFST.DEFAULTS = {
14424     "version": "1.0.0"
14425 };
14426 /* ======================================================================
14427     OpenLayers/Filter.js
14428    ====================================================================== */
14429
14430 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
14431  * full list of contributors). Published under the 2-clause BSD license.
14432  * See license.txt in the OpenLayers distribution or repository for the
14433  * full text of the license. */
14434
14435
14436 /**
14437  * @requires OpenLayers/BaseTypes/Class.js
14438  * @requires OpenLayers/Util.js
14439  * @requires OpenLayers/Style.js
14440  */
14441
14442 /**
14443  * Class: OpenLayers.Filter
14444  * This class represents an OGC Filter.
14445  */
14446 OpenLayers.Filter = OpenLayers.Class({
14447     
14448     /** 
14449      * Constructor: OpenLayers.Filter
14450      * This class represents a generic filter.
14451      *
14452      * Parameters:
14453      * options - {Object} Optional object whose properties will be set on the
14454      *     instance.
14455      * 
14456      * Returns:
14457      * {<OpenLayers.Filter>}
14458      */
14459     initialize: function(options) {
14460         OpenLayers.Util.extend(this, options);
14461     },
14462
14463     /** 
14464      * APIMethod: destroy
14465      * Remove reference to anything added.
14466      */
14467     destroy: function() {
14468     },
14469
14470     /**
14471      * APIMethod: evaluate
14472      * Evaluates this filter in a specific context.  Instances or subclasses
14473      * are supposed to override this method.
14474      * 
14475      * Parameters:
14476      * context - {Object} Context to use in evaluating the filter.  If a vector
14477      *     feature is provided, the feature.attributes will be used as context.
14478      * 
14479      * Returns:
14480      * {Boolean} The filter applies.
14481      */
14482     evaluate: function(context) {
14483         return true;
14484     },
14485     
14486     /**
14487      * APIMethod: clone
14488      * Clones this filter. Should be implemented by subclasses.
14489      * 
14490      * Returns:
14491      * {<OpenLayers.Filter>} Clone of this filter.
14492      */
14493     clone: function() {
14494         return null;
14495     },
14496     
14497     /**
14498      * APIMethod: toString
14499      *
14500      * Returns:
14501      * {String} Include <OpenLayers.Format.CQL> in your build to get a CQL
14502      *     representation of the filter returned. Otherwise "[Object object]"
14503      *     will be returned.
14504      */
14505     toString: function() {
14506         var string;
14507         if (OpenLayers.Format && OpenLayers.Format.CQL) {
14508             string = OpenLayers.Format.CQL.prototype.write(this);
14509         } else {
14510             string = Object.prototype.toString.call(this);
14511         }
14512         return string;
14513     },
14514     
14515     CLASS_NAME: "OpenLayers.Filter"
14516 });
14517 /* ======================================================================
14518     OpenLayers/Filter/Spatial.js
14519    ====================================================================== */
14520
14521 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
14522  * full list of contributors). Published under the 2-clause BSD license.
14523  * See license.txt in the OpenLayers distribution or repository for the
14524  * full text of the license. */
14525
14526 /**
14527  * @requires OpenLayers/Filter.js
14528  */
14529
14530 /**
14531  * Class: OpenLayers.Filter.Spatial
14532  * This class represents a spatial filter.
14533  * Currently implemented: BBOX, DWithin and Intersects
14534  * 
14535  * Inherits from:
14536  * - <OpenLayers.Filter>
14537  */
14538 OpenLayers.Filter.Spatial = OpenLayers.Class(OpenLayers.Filter, {
14539
14540     /**
14541      * APIProperty: type
14542      * {String} Type of spatial filter.
14543      *
14544      * The type should be one of:
14545      * - OpenLayers.Filter.Spatial.BBOX
14546      * - OpenLayers.Filter.Spatial.INTERSECTS
14547      * - OpenLayers.Filter.Spatial.DWITHIN
14548      * - OpenLayers.Filter.Spatial.WITHIN
14549      * - OpenLayers.Filter.Spatial.CONTAINS
14550      */
14551     type: null,
14552     
14553     /**
14554      * APIProperty: property
14555      * {String} Name of the context property to compare.
14556      */
14557     property: null,
14558     
14559     /**
14560      * APIProperty: value
14561      * {<OpenLayers.Bounds> || <OpenLayers.Geometry>} The bounds or geometry
14562      *     to be used by the filter.  Use bounds for BBOX filters and geometry
14563      *     for INTERSECTS or DWITHIN filters.
14564      */
14565     value: null,
14566
14567     /**
14568      * APIProperty: distance
14569      * {Number} The distance to use in a DWithin spatial filter.
14570      */
14571     distance: null,
14572
14573     /**
14574      * APIProperty: distanceUnits
14575      * {String} The units to use for the distance, e.g. 'm'.
14576      */
14577     distanceUnits: null,
14578     
14579     /** 
14580      * Constructor: OpenLayers.Filter.Spatial
14581      * Creates a spatial filter.
14582      *
14583      * Parameters:
14584      * options - {Object} An optional object with properties to set on the
14585      *     filter.
14586      * 
14587      * Returns:
14588      * {<OpenLayers.Filter.Spatial>}
14589      */
14590
14591    /**
14592     * Method: evaluate
14593     * Evaluates this filter for a specific feature.
14594     * 
14595     * Parameters:
14596     * feature - {<OpenLayers.Feature.Vector>} feature to apply the filter to.
14597     * 
14598     * Returns:
14599     * {Boolean} The feature meets filter criteria.
14600     */
14601     evaluate: function(feature) {
14602         var intersect = false;
14603         switch(this.type) {
14604             case OpenLayers.Filter.Spatial.BBOX:
14605             case OpenLayers.Filter.Spatial.INTERSECTS:
14606                 if(feature.geometry) {
14607                     var geom = this.value;
14608                     if(this.value.CLASS_NAME == "OpenLayers.Bounds") {
14609                         geom = this.value.toGeometry();
14610                     }
14611                     if(feature.geometry.intersects(geom)) {
14612                         intersect = true;
14613                     }
14614                 }
14615                 break;
14616             default:
14617                 throw new Error('evaluate is not implemented for this filter type.');
14618         }
14619         return intersect;
14620     },
14621
14622     /**
14623      * APIMethod: clone
14624      * Clones this filter.
14625      * 
14626      * Returns:
14627      * {<OpenLayers.Filter.Spatial>} Clone of this filter.
14628      */
14629     clone: function() {
14630         var options = OpenLayers.Util.applyDefaults({
14631             value: this.value && this.value.clone && this.value.clone()
14632         }, this);
14633         return new OpenLayers.Filter.Spatial(options);
14634     },
14635     CLASS_NAME: "OpenLayers.Filter.Spatial"
14636 });
14637
14638 OpenLayers.Filter.Spatial.BBOX = "BBOX";
14639 OpenLayers.Filter.Spatial.INTERSECTS = "INTERSECTS";
14640 OpenLayers.Filter.Spatial.DWITHIN = "DWITHIN";
14641 OpenLayers.Filter.Spatial.WITHIN = "WITHIN";
14642 OpenLayers.Filter.Spatial.CONTAINS = "CONTAINS";
14643 /* ======================================================================
14644     OpenLayers/Filter/FeatureId.js
14645    ====================================================================== */
14646
14647 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
14648  * full list of contributors). Published under the 2-clause BSD license.
14649  * See license.txt in the OpenLayers distribution or repository for the
14650  * full text of the license. */
14651
14652
14653 /**
14654  * @requires OpenLayers/Filter.js
14655  */
14656
14657 /**
14658  * Class: OpenLayers.Filter.FeatureId
14659  * This class represents a ogc:FeatureId Filter, as being used for rule-based SLD
14660  * styling
14661  * 
14662  * Inherits from:
14663  * - <OpenLayers.Filter>
14664  */
14665 OpenLayers.Filter.FeatureId = OpenLayers.Class(OpenLayers.Filter, {
14666
14667     /** 
14668      * APIProperty: fids
14669      * {Array(String)} Feature Ids to evaluate this rule against. 
14670      *     To be passed inside the params object.
14671      */
14672     fids: null,
14673     
14674     /** 
14675      * Property: type
14676      * {String} Type to identify this filter.
14677      */
14678     type: "FID",
14679     
14680     /** 
14681      * Constructor: OpenLayers.Filter.FeatureId
14682      * Creates an ogc:FeatureId rule.
14683      *
14684      * Parameters:
14685      * options - {Object} An optional object with properties to set on the
14686      *           rule
14687      * 
14688      * Returns:
14689      * {<OpenLayers.Filter.FeatureId>}
14690      */
14691     initialize: function(options) {
14692         this.fids = [];
14693         OpenLayers.Filter.prototype.initialize.apply(this, [options]);
14694     },
14695
14696     /**
14697      * APIMethod: evaluate
14698      * evaluates this rule for a specific feature
14699      * 
14700      * Parameters:
14701      * feature - {<OpenLayers.Feature>} feature to apply the rule to.
14702      *           For vector features, the check is run against the fid,
14703      *           for plain features against the id.
14704      * 
14705      * Returns:
14706      * {Boolean} true if the rule applies, false if it does not
14707      */
14708     evaluate: function(feature) {
14709         for (var i=0, len=this.fids.length; i<len; i++) {
14710             var fid = feature.fid || feature.id;
14711             if (fid == this.fids[i]) {
14712                 return true;
14713             }
14714         }
14715         return false;
14716     },
14717     
14718     /**
14719      * APIMethod: clone
14720      * Clones this filter.
14721      * 
14722      * Returns:
14723      * {<OpenLayers.Filter.FeatureId>} Clone of this filter.
14724      */
14725     clone: function() {
14726         var filter = new OpenLayers.Filter.FeatureId();
14727         OpenLayers.Util.extend(filter, this);
14728         filter.fids = this.fids.slice();
14729         return filter;
14730     },
14731     
14732     CLASS_NAME: "OpenLayers.Filter.FeatureId"
14733 });
14734 /* ======================================================================
14735     OpenLayers/Format/WFST/v1.js
14736    ====================================================================== */
14737
14738 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
14739  * full list of contributors). Published under the 2-clause BSD license.
14740  * See license.txt in the OpenLayers distribution or repository for the
14741  * full text of the license. */
14742
14743 /**
14744  * @requires OpenLayers/Format/XML.js
14745  * @requires OpenLayers/Format/WFST.js
14746  * @requires OpenLayers/Filter/Spatial.js
14747  * @requires OpenLayers/Filter/FeatureId.js
14748  */
14749
14750 /**
14751  * Class: OpenLayers.Format.WFST.v1
14752  * Superclass for WFST parsers.
14753  *
14754  * Inherits from:
14755  *  - <OpenLayers.Format.XML>
14756  */
14757 OpenLayers.Format.WFST.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
14758     
14759     /**
14760      * Property: namespaces
14761      * {Object} Mapping of namespace aliases to namespace URIs.
14762      */
14763     namespaces: {
14764         xlink: "http://www.w3.org/1999/xlink",
14765         xsi: "http://www.w3.org/2001/XMLSchema-instance",
14766         wfs: "http://www.opengis.net/wfs",
14767         gml: "http://www.opengis.net/gml",
14768         ogc: "http://www.opengis.net/ogc",
14769         ows: "http://www.opengis.net/ows",
14770         xmlns: "http://www.w3.org/2000/xmlns/"
14771     },
14772     
14773     /**
14774      * Property: defaultPrefix
14775      */
14776     defaultPrefix: "wfs",
14777
14778     /**
14779      * Property: version
14780      * {String} WFS version number.
14781      */
14782     version: null,
14783
14784     /**
14785      * Property: schemaLocation
14786      * {String} Schema location for a particular minor version.
14787      */
14788     schemaLocations: null,
14789     
14790     /**
14791      * APIProperty: srsName
14792      * {String} URI for spatial reference system.
14793      */
14794     srsName: null,
14795
14796     /**
14797      * APIProperty: extractAttributes
14798      * {Boolean} Extract attributes from GML.  Default is true.
14799      */
14800     extractAttributes: true,
14801     
14802     /**
14803      * APIProperty: xy
14804      * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
14805      * Changing is not recommended, a new Format should be instantiated.
14806      */ 
14807     xy: true,
14808
14809     /**
14810      * Property: stateName
14811      * {Object} Maps feature states to node names.
14812      */
14813     stateName: null,
14814     
14815     /**
14816      * Constructor: OpenLayers.Format.WFST.v1
14817      * Instances of this class are not created directly.  Use the
14818      *     <OpenLayers.Format.WFST.v1_0_0> or <OpenLayers.Format.WFST.v1_1_0>
14819      *     constructor instead.
14820      *
14821      * Parameters:
14822      * options - {Object} An optional object whose properties will be set on
14823      *     this instance.
14824      */
14825     initialize: function(options) {
14826         // set state name mapping
14827         this.stateName = {};
14828         this.stateName[OpenLayers.State.INSERT] = "wfs:Insert";
14829         this.stateName[OpenLayers.State.UPDATE] = "wfs:Update";
14830         this.stateName[OpenLayers.State.DELETE] = "wfs:Delete";
14831         OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
14832     },
14833     
14834     /**
14835      * Method: getSrsName
14836      */
14837     getSrsName: function(feature, options) {
14838         var srsName = options && options.srsName;
14839         if(!srsName) {
14840             if(feature && feature.layer) {
14841                 srsName = feature.layer.projection.getCode();
14842             } else {
14843                 srsName = this.srsName;
14844             }
14845         }
14846         return srsName;
14847     },
14848
14849     /**
14850      * APIMethod: read
14851      * Parse the response from a transaction.  Because WFS is split into
14852      *     Transaction requests (create, update, and delete) and GetFeature
14853      *     requests (read), this method handles parsing of both types of
14854      *     responses.
14855      *
14856      * Parameters:
14857      * data - {String | Document} The WFST document to read
14858      * options - {Object} Options for the reader
14859      *
14860      * Valid options properties:
14861      * output - {String} either "features" or "object". The default is
14862      *     "features", which means that the method will return an array of
14863      *     features. If set to "object", an object with a "features" property
14864      *     and other properties read by the parser will be returned.
14865      *
14866      * Returns:
14867      * {Array | Object} Output depending on the output option.
14868      */
14869     read: function(data, options) {
14870         options = options || {};
14871         OpenLayers.Util.applyDefaults(options, {
14872             output: "features"
14873         });
14874         
14875         if(typeof data == "string") { 
14876             data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
14877         }
14878         if(data && data.nodeType == 9) {
14879             data = data.documentElement;
14880         }
14881         var obj = {};
14882         if(data) {
14883             this.readNode(data, obj, true);
14884         }
14885         if(obj.features && options.output === "features") {
14886             obj = obj.features;
14887         }
14888         return obj;
14889     },
14890     
14891     /**
14892      * Property: readers
14893      * Contains public functions, grouped by namespace prefix, that will
14894      *     be applied when a namespaced node is found matching the function
14895      *     name.  The function will be applied in the scope of this parser
14896      *     with two arguments: the node being read and a context object passed
14897      *     from the parent.
14898      */
14899     readers: {
14900         "wfs": {
14901             "FeatureCollection": function(node, obj) {
14902                 obj.features = [];
14903                 this.readChildNodes(node, obj);
14904             }
14905         }
14906     },
14907     
14908     /**
14909      * Method: write
14910      * Given an array of features, write a WFS transaction.  This assumes
14911      *     the features have a state property that determines the operation
14912      *     type - insert, update, or delete.
14913      *
14914      * Parameters:
14915      * features - {Array(<OpenLayers.Feature.Vector>)} A list of features. See
14916      *     below for a more detailed description of the influence of the
14917      *     feature's *modified* property.
14918      * options - {Object}
14919      *
14920      * feature.modified rules:
14921      * If a feature has a modified property set, the following checks will be
14922      * made before a feature's geometry or attribute is included in an Update
14923      * transaction:
14924      * - *modified* is not set at all: The geometry and all attributes will be
14925      *     included.
14926      * - *modified.geometry* is set (null or a geometry): The geometry will be
14927      *     included. If *modified.attributes* is not set, all attributes will
14928      *     be included.
14929      * - *modified.attributes* is set: Only the attributes set in 
14930      *     *modified.attributes* will be included.
14931      *     If *modified.geometry* is not set, the geometry will not be included.
14932      *
14933      * Valid options include:
14934      * - *multi* {Boolean} If set to true, geometries will be casted to
14935      *   Multi geometries before writing.
14936      *
14937      * Returns:
14938      * {String} A serialized WFS transaction.
14939      */
14940     write: function(features, options) {
14941         var node = this.writeNode("wfs:Transaction", {
14942             features:features,
14943             options: options
14944         });
14945         var value = this.schemaLocationAttr();
14946         if(value) {
14947             this.setAttributeNS(
14948                 node, this.namespaces["xsi"], "xsi:schemaLocation",  value
14949             );
14950         }
14951         return OpenLayers.Format.XML.prototype.write.apply(this, [node]);
14952     },
14953     
14954     /**
14955      * Property: writers
14956      * As a compliment to the readers property, this structure contains public
14957      *     writing functions grouped by namespace alias and named like the
14958      *     node names they produce.
14959      */
14960     writers: {
14961         "wfs": {
14962             "GetFeature": function(options) {
14963                 var node = this.createElementNSPlus("wfs:GetFeature", {
14964                     attributes: {
14965                         service: "WFS",
14966                         version: this.version,
14967                         handle: options && options.handle,
14968                         outputFormat: options && options.outputFormat,
14969                         maxFeatures: options && options.maxFeatures,
14970                         viewParams: options && options.viewParams,
14971                         "xsi:schemaLocation": this.schemaLocationAttr(options)
14972                     }
14973                 });
14974                 if (typeof this.featureType == "string") {
14975                     this.writeNode("Query", options, node);
14976                 } else {
14977                     for (var i=0,len = this.featureType.length; i<len; i++) { 
14978                         options.featureType = this.featureType[i]; 
14979                         this.writeNode("Query", options, node); 
14980                     } 
14981                 }
14982                 return node;
14983             },
14984             "Transaction": function(obj) {
14985                 obj = obj || {};
14986                 var options = obj.options || {};
14987                 var node = this.createElementNSPlus("wfs:Transaction", {
14988                     attributes: {
14989                         service: "WFS",
14990                         version: this.version,
14991                         handle: options.handle
14992                     }
14993                 });
14994                 var i, len;
14995                 var features = obj.features;
14996                 if(features) {
14997                     // temporarily re-assigning geometry types
14998                     if (options.multi === true) {
14999                         OpenLayers.Util.extend(this.geometryTypes, {
15000                             "OpenLayers.Geometry.Point": "MultiPoint",
15001                             "OpenLayers.Geometry.LineString": (this.multiCurve === true) ? "MultiCurve": "MultiLineString",
15002                             "OpenLayers.Geometry.Polygon": (this.multiSurface === true) ? "MultiSurface" : "MultiPolygon"
15003                         });
15004                     }
15005                     var name, feature;
15006                     for(i=0, len=features.length; i<len; ++i) {
15007                         feature = features[i];
15008                         name = this.stateName[feature.state];
15009                         if(name) {
15010                             this.writeNode(name, {
15011                                 feature: feature, 
15012                                 options: options
15013                             }, node);
15014                         }
15015                     }
15016                     // switch back to original geometry types assignment
15017                     if (options.multi === true) {
15018                         this.setGeometryTypes();
15019                     }
15020                 }
15021                 if (options.nativeElements) {
15022                     for (i=0, len=options.nativeElements.length; i<len; ++i) {
15023                         this.writeNode("wfs:Native", 
15024                             options.nativeElements[i], node);
15025                     }
15026                 }
15027                 return node;
15028             },
15029             "Native": function(nativeElement) {
15030                 var node = this.createElementNSPlus("wfs:Native", {
15031                     attributes: {
15032                         vendorId: nativeElement.vendorId,
15033                         safeToIgnore: nativeElement.safeToIgnore
15034                     },
15035                     value: nativeElement.value
15036                 });
15037                 return node;
15038             },
15039             "Insert": function(obj) {
15040                 var feature = obj.feature;
15041                 var options = obj.options;
15042                 var node = this.createElementNSPlus("wfs:Insert", {
15043                     attributes: {
15044                         handle: options && options.handle
15045                     }
15046                 });
15047                 this.srsName = this.getSrsName(feature);
15048                 this.writeNode("feature:_typeName", feature, node);
15049                 return node;
15050             },
15051             "Update": function(obj) {
15052                 var feature = obj.feature;
15053                 var options = obj.options;
15054                 var node = this.createElementNSPlus("wfs:Update", {
15055                     attributes: {
15056                         handle: options && options.handle,
15057                         typeName: (this.featureNS ? this.featurePrefix + ":" : "") +
15058                             this.featureType
15059                     }
15060                 });
15061                 if(this.featureNS) {
15062                     this.setAttributeNS(
15063                         node, this.namespaces.xmlns,
15064                         "xmlns:" + this.featurePrefix, this.featureNS
15065                     );
15066                 }
15067                 
15068                 // add in geometry
15069                 var modified = feature.modified;
15070                 if (this.geometryName !== null && (!modified || modified.geometry !== undefined)) {
15071                     this.srsName = this.getSrsName(feature);
15072                     this.writeNode(
15073                         "Property", {name: this.geometryName, value: feature.geometry}, node
15074                     );
15075                 }
15076         
15077                 // add in attributes
15078                 for(var key in feature.attributes) {
15079                     if(feature.attributes[key] !== undefined &&
15080                                 (!modified || !modified.attributes ||
15081                                 (modified.attributes && (key in modified.attributes)))) {
15082                         this.writeNode(
15083                             "Property", {name: key, value: feature.attributes[key]}, node
15084                         );
15085                     }
15086                 }
15087                 
15088                 // add feature id filter
15089                 this.writeNode("ogc:Filter", new OpenLayers.Filter.FeatureId({
15090                     fids: [feature.fid]
15091                 }), node);
15092         
15093                 return node;
15094             },
15095             "Property": function(obj) {
15096                 var node = this.createElementNSPlus("wfs:Property");
15097                 this.writeNode("Name", obj.name, node);
15098                 if(obj.value !== null) {
15099                     this.writeNode("Value", obj.value, node);
15100                 }
15101                 return node;
15102             },
15103             "Name": function(name) {
15104                 return this.createElementNSPlus("wfs:Name", {value: name});
15105             },
15106             "Value": function(obj) {
15107                 var node;
15108                 if(obj instanceof OpenLayers.Geometry) {
15109                     node = this.createElementNSPlus("wfs:Value");
15110                     var geom = this.writeNode("feature:_geometry", obj).firstChild;
15111                     node.appendChild(geom);
15112                 } else {
15113                     node = this.createElementNSPlus("wfs:Value", {value: obj});                
15114                 }
15115                 return node;
15116             },
15117             "Delete": function(obj) {
15118                 var feature = obj.feature;
15119                 var options = obj.options;
15120                 var node = this.createElementNSPlus("wfs:Delete", {
15121                     attributes: {
15122                         handle: options && options.handle,
15123                         typeName: (this.featureNS ? this.featurePrefix + ":" : "") +
15124                             this.featureType
15125                     }
15126                 });
15127                 if(this.featureNS) {
15128                     this.setAttributeNS(
15129                         node, this.namespaces.xmlns,
15130                         "xmlns:" + this.featurePrefix, this.featureNS
15131                     );
15132                 }
15133                 this.writeNode("ogc:Filter", new OpenLayers.Filter.FeatureId({
15134                     fids: [feature.fid]
15135                 }), node);
15136                 return node;
15137             }
15138         }
15139     },
15140
15141     /**
15142      * Method: schemaLocationAttr
15143      * Generate the xsi:schemaLocation attribute value.
15144      *
15145      * Returns:
15146      * {String} The xsi:schemaLocation attribute or undefined if none.
15147      */
15148     schemaLocationAttr: function(options) {
15149         options = OpenLayers.Util.extend({
15150             featurePrefix: this.featurePrefix,
15151             schema: this.schema
15152         }, options);
15153         var schemaLocations = OpenLayers.Util.extend({}, this.schemaLocations);
15154         if(options.schema) {
15155             schemaLocations[options.featurePrefix] = options.schema;
15156         }
15157         var parts = [];
15158         var uri;
15159         for(var key in schemaLocations) {
15160             uri = this.namespaces[key];
15161             if(uri) {
15162                 parts.push(uri + " " + schemaLocations[key]);
15163             }
15164         }
15165         var value = parts.join(" ") || undefined;
15166         return value;
15167     },
15168     
15169     /**
15170      * Method: setFilterProperty
15171      * Set the property of each spatial filter.
15172      *
15173      * Parameters:
15174      * filter - {<OpenLayers.Filter>}
15175      */
15176     setFilterProperty: function(filter) {
15177         if(filter.filters) {
15178             for(var i=0, len=filter.filters.length; i<len; ++i) {
15179                 OpenLayers.Format.WFST.v1.prototype.setFilterProperty.call(this, filter.filters[i]);
15180             }
15181         } else {
15182             if(filter instanceof OpenLayers.Filter.Spatial && !filter.property) {
15183                 // got a spatial filter without property, so set it
15184                 filter.property = this.geometryName;
15185             }
15186         }
15187     },
15188
15189     CLASS_NAME: "OpenLayers.Format.WFST.v1" 
15190
15191 });
15192 /* ======================================================================
15193     OpenLayers/Geometry.js
15194    ====================================================================== */
15195
15196 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
15197  * full list of contributors). Published under the 2-clause BSD license.
15198  * See license.txt in the OpenLayers distribution or repository for the
15199  * full text of the license. */
15200  
15201 /**
15202  * @requires OpenLayers/BaseTypes/Class.js
15203  */
15204
15205 /**
15206  * Class: OpenLayers.Geometry
15207  * A Geometry is a description of a geographic object.  Create an instance of
15208  * this class with the <OpenLayers.Geometry> constructor.  This is a base class,
15209  * typical geometry types are described by subclasses of this class.
15210  *
15211  * Note that if you use the <OpenLayers.Geometry.fromWKT> method, you must
15212  * explicitly include the OpenLayers.Format.WKT in your build.
15213  */
15214 OpenLayers.Geometry = OpenLayers.Class({
15215
15216     /**
15217      * Property: id
15218      * {String} A unique identifier for this geometry.
15219      */
15220     id: null,
15221
15222     /**
15223      * Property: parent
15224      * {<OpenLayers.Geometry>}This is set when a Geometry is added as component
15225      * of another geometry
15226      */
15227     parent: null,
15228
15229     /**
15230      * Property: bounds 
15231      * {<OpenLayers.Bounds>} The bounds of this geometry
15232      */
15233     bounds: null,
15234
15235     /**
15236      * Constructor: OpenLayers.Geometry
15237      * Creates a geometry object.  
15238      */
15239     initialize: function() {
15240         this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME+ "_");
15241     },
15242     
15243     /**
15244      * Method: destroy
15245      * Destroy this geometry.
15246      */
15247     destroy: function() {
15248         this.id = null;
15249         this.bounds = null;
15250     },
15251     
15252     /**
15253      * APIMethod: clone
15254      * Create a clone of this geometry.  Does not set any non-standard
15255      *     properties of the cloned geometry.
15256      * 
15257      * Returns:
15258      * {<OpenLayers.Geometry>} An exact clone of this geometry.
15259      */
15260     clone: function() {
15261         return new OpenLayers.Geometry();
15262     },
15263     
15264     /**
15265      * Method: setBounds
15266      * Set the bounds for this Geometry.
15267      * 
15268      * Parameters:
15269      * bounds - {<OpenLayers.Bounds>} 
15270      */
15271     setBounds: function(bounds) {
15272         if (bounds) {
15273             this.bounds = bounds.clone();
15274         }
15275     },
15276     
15277     /**
15278      * Method: clearBounds
15279      * Nullify this components bounds and that of its parent as well.
15280      */
15281     clearBounds: function() {
15282         this.bounds = null;
15283         if (this.parent) {
15284             this.parent.clearBounds();
15285         }    
15286     },
15287     
15288     /**
15289      * Method: extendBounds
15290      * Extend the existing bounds to include the new bounds. 
15291      * If geometry's bounds is not yet set, then set a new Bounds.
15292      * 
15293      * Parameters:
15294      * newBounds - {<OpenLayers.Bounds>} 
15295      */
15296     extendBounds: function(newBounds){
15297         var bounds = this.getBounds();
15298         if (!bounds) {
15299             this.setBounds(newBounds);
15300         } else {
15301             this.bounds.extend(newBounds);
15302         }
15303     },
15304     
15305     /**
15306      * APIMethod: getBounds
15307      * Get the bounds for this Geometry. If bounds is not set, it 
15308      * is calculated again, this makes queries faster.
15309      * 
15310      * Returns:
15311      * {<OpenLayers.Bounds>}
15312      */
15313     getBounds: function() {
15314         if (this.bounds == null) {
15315             this.calculateBounds();
15316         }
15317         return this.bounds;
15318     },
15319     
15320     /** 
15321      * APIMethod: calculateBounds
15322      * Recalculate the bounds for the geometry. 
15323      */
15324     calculateBounds: function() {
15325         //
15326         // This should be overridden by subclasses.
15327         //
15328     },
15329     
15330     /**
15331      * APIMethod: distanceTo
15332      * Calculate the closest distance between two geometries (on the x-y plane).
15333      *
15334      * Parameters:
15335      * geometry - {<OpenLayers.Geometry>} The target geometry.
15336      * options - {Object} Optional properties for configuring the distance
15337      *     calculation.
15338      *
15339      * Valid options depend on the specific geometry type.
15340      * 
15341      * Returns:
15342      * {Number | Object} The distance between this geometry and the target.
15343      *     If details is true, the return will be an object with distance,
15344      *     x0, y0, x1, and x2 properties.  The x0 and y0 properties represent
15345      *     the coordinates of the closest point on this geometry. The x1 and y1
15346      *     properties represent the coordinates of the closest point on the
15347      *     target geometry.
15348      */
15349     distanceTo: function(geometry, options) {
15350     },
15351     
15352     /**
15353      * APIMethod: getVertices
15354      * Return a list of all points in this geometry.
15355      *
15356      * Parameters:
15357      * nodes - {Boolean} For lines, only return vertices that are
15358      *     endpoints.  If false, for lines, only vertices that are not
15359      *     endpoints will be returned.  If not provided, all vertices will
15360      *     be returned.
15361      *
15362      * Returns:
15363      * {Array} A list of all vertices in the geometry.
15364      */
15365     getVertices: function(nodes) {
15366     },
15367
15368     /**
15369      * Method: atPoint
15370      * Note - This is only an approximation based on the bounds of the 
15371      * geometry.
15372      * 
15373      * Parameters:
15374      * lonlat - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
15375      *     object with a 'lon' and 'lat' properties.
15376      * toleranceLon - {float} Optional tolerance in Geometric Coords
15377      * toleranceLat - {float} Optional tolerance in Geographic Coords
15378      * 
15379      * Returns:
15380      * {Boolean} Whether or not the geometry is at the specified location
15381      */
15382     atPoint: function(lonlat, toleranceLon, toleranceLat) {
15383         var atPoint = false;
15384         var bounds = this.getBounds();
15385         if ((bounds != null) && (lonlat != null)) {
15386
15387             var dX = (toleranceLon != null) ? toleranceLon : 0;
15388             var dY = (toleranceLat != null) ? toleranceLat : 0;
15389     
15390             var toleranceBounds = 
15391                 new OpenLayers.Bounds(this.bounds.left - dX,
15392                                       this.bounds.bottom - dY,
15393                                       this.bounds.right + dX,
15394                                       this.bounds.top + dY);
15395
15396             atPoint = toleranceBounds.containsLonLat(lonlat);
15397         }
15398         return atPoint;
15399     },
15400     
15401     /**
15402      * Method: getLength
15403      * Calculate the length of this geometry. This method is defined in
15404      * subclasses.
15405      * 
15406      * Returns:
15407      * {Float} The length of the collection by summing its parts
15408      */
15409     getLength: function() {
15410         //to be overridden by geometries that actually have a length
15411         //
15412         return 0.0;
15413     },
15414
15415     /**
15416      * Method: getArea
15417      * Calculate the area of this geometry. This method is defined in subclasses.
15418      * 
15419      * Returns:
15420      * {Float} The area of the collection by summing its parts
15421      */
15422     getArea: function() {
15423         //to be overridden by geometries that actually have an area
15424         //
15425         return 0.0;
15426     },
15427     
15428     /**
15429      * APIMethod: getCentroid
15430      * Calculate the centroid of this geometry. This method is defined in subclasses.
15431      *
15432      * Returns:
15433      * {<OpenLayers.Geometry.Point>} The centroid of the collection
15434      */
15435     getCentroid: function() {
15436         return null;
15437     },
15438
15439     /**
15440      * Method: toString
15441      * Returns a text representation of the geometry.  If the WKT format is
15442      *     included in a build, this will be the Well-Known Text 
15443      *     representation.
15444      *
15445      * Returns:
15446      * {String} String representation of this geometry.
15447      */
15448     toString: function() {
15449         var string;
15450         if (OpenLayers.Format && OpenLayers.Format.WKT) {
15451             string = OpenLayers.Format.WKT.prototype.write(
15452                 new OpenLayers.Feature.Vector(this)
15453             );
15454         } else {
15455             string = Object.prototype.toString.call(this);
15456         }
15457         return string;
15458     },
15459
15460     CLASS_NAME: "OpenLayers.Geometry"
15461 });
15462
15463 /**
15464  * Function: OpenLayers.Geometry.fromWKT
15465  * Generate a geometry given a Well-Known Text string.  For this method to
15466  *     work, you must include the OpenLayers.Format.WKT in your build 
15467  *     explicitly.
15468  *
15469  * Parameters:
15470  * wkt - {String} A string representing the geometry in Well-Known Text.
15471  *
15472  * Returns:
15473  * {<OpenLayers.Geometry>} A geometry of the appropriate class.
15474  */
15475 OpenLayers.Geometry.fromWKT = function(wkt) {
15476     var geom;
15477     if (OpenLayers.Format && OpenLayers.Format.WKT) {
15478         var format = OpenLayers.Geometry.fromWKT.format;
15479         if (!format) {
15480             format = new OpenLayers.Format.WKT();
15481             OpenLayers.Geometry.fromWKT.format = format;
15482         }
15483         var result = format.read(wkt);
15484         if (result instanceof OpenLayers.Feature.Vector) {
15485             geom = result.geometry;
15486         } else if (OpenLayers.Util.isArray(result)) {
15487             var len = result.length;
15488             var components = new Array(len);
15489             for (var i=0; i<len; ++i) {
15490                 components[i] = result[i].geometry;
15491             }
15492             geom = new OpenLayers.Geometry.Collection(components);
15493         }
15494     }
15495     return geom;
15496 };
15497     
15498 /**
15499  * Method: OpenLayers.Geometry.segmentsIntersect
15500  * Determine whether two line segments intersect.  Optionally calculates
15501  *     and returns the intersection point.  This function is optimized for
15502  *     cases where seg1.x2 >= seg2.x1 || seg2.x2 >= seg1.x1.  In those
15503  *     obvious cases where there is no intersection, the function should
15504  *     not be called.
15505  *
15506  * Parameters:
15507  * seg1 - {Object} Object representing a segment with properties x1, y1, x2,
15508  *     and y2.  The start point is represented by x1 and y1.  The end point
15509  *     is represented by x2 and y2.  Start and end are ordered so that x1 < x2.
15510  * seg2 - {Object} Object representing a segment with properties x1, y1, x2,
15511  *     and y2.  The start point is represented by x1 and y1.  The end point
15512  *     is represented by x2 and y2.  Start and end are ordered so that x1 < x2.
15513  * options - {Object} Optional properties for calculating the intersection.
15514  *
15515  * Valid options:
15516  * point - {Boolean} Return the intersection point.  If false, the actual
15517  *     intersection point will not be calculated.  If true and the segments
15518  *     intersect, the intersection point will be returned.  If true and
15519  *     the segments do not intersect, false will be returned.  If true and
15520  *     the segments are coincident, true will be returned.
15521  * tolerance - {Number} If a non-null value is provided, if the segments are
15522  *     within the tolerance distance, this will be considered an intersection.
15523  *     In addition, if the point option is true and the calculated intersection
15524  *     is within the tolerance distance of an end point, the endpoint will be
15525  *     returned instead of the calculated intersection.  Further, if the
15526  *     intersection is within the tolerance of endpoints on both segments, or
15527  *     if two segment endpoints are within the tolerance distance of eachother
15528  *     (but no intersection is otherwise calculated), an endpoint on the
15529  *     first segment provided will be returned.
15530  *
15531  * Returns:
15532  * {Boolean | <OpenLayers.Geometry.Point>}  The two segments intersect.
15533  *     If the point argument is true, the return will be the intersection
15534  *     point or false if none exists.  If point is true and the segments
15535  *     are coincident, return will be true (and the instersection is equal
15536  *     to the shorter segment).
15537  */
15538 OpenLayers.Geometry.segmentsIntersect = function(seg1, seg2, options) {
15539     var point = options && options.point;
15540     var tolerance = options && options.tolerance;
15541     var intersection = false;
15542     var x11_21 = seg1.x1 - seg2.x1;
15543     var y11_21 = seg1.y1 - seg2.y1;
15544     var x12_11 = seg1.x2 - seg1.x1;
15545     var y12_11 = seg1.y2 - seg1.y1;
15546     var y22_21 = seg2.y2 - seg2.y1;
15547     var x22_21 = seg2.x2 - seg2.x1;
15548     var d = (y22_21 * x12_11) - (x22_21 * y12_11);
15549     var n1 = (x22_21 * y11_21) - (y22_21 * x11_21);
15550     var n2 = (x12_11 * y11_21) - (y12_11 * x11_21);
15551     if(d == 0) {
15552         // parallel
15553         if(n1 == 0 && n2 == 0) {
15554             // coincident
15555             intersection = true;
15556         }
15557     } else {
15558         var along1 = n1 / d;
15559         var along2 = n2 / d;
15560         if(along1 >= 0 && along1 <= 1 && along2 >=0 && along2 <= 1) {
15561             // intersect
15562             if(!point) {
15563                 intersection = true;
15564             } else {
15565                 // calculate the intersection point
15566                 var x = seg1.x1 + (along1 * x12_11);
15567                 var y = seg1.y1 + (along1 * y12_11);
15568                 intersection = new OpenLayers.Geometry.Point(x, y);
15569             }
15570         }
15571     }
15572     if(tolerance) {
15573         var dist;
15574         if(intersection) {
15575             if(point) {
15576                 var segs = [seg1, seg2];
15577                 var seg, x, y;
15578                 // check segment endpoints for proximity to intersection
15579                 // set intersection to first endpoint within the tolerance
15580                 outer: for(var i=0; i<2; ++i) {
15581                     seg = segs[i];
15582                     for(var j=1; j<3; ++j) {
15583                         x = seg["x" + j];
15584                         y = seg["y" + j];
15585                         dist = Math.sqrt(
15586                             Math.pow(x - intersection.x, 2) +
15587                             Math.pow(y - intersection.y, 2)
15588                         );
15589                         if(dist < tolerance) {
15590                             intersection.x = x;
15591                             intersection.y = y;
15592                             break outer;
15593                         }
15594                     }
15595                 }
15596                 
15597             }
15598         } else {
15599             // no calculated intersection, but segments could be within
15600             // the tolerance of one another
15601             var segs = [seg1, seg2];
15602             var source, target, x, y, p, result;
15603             // check segment endpoints for proximity to intersection
15604             // set intersection to first endpoint within the tolerance
15605             outer: for(var i=0; i<2; ++i) {
15606                 source = segs[i];
15607                 target = segs[(i+1)%2];
15608                 for(var j=1; j<3; ++j) {
15609                     p = {x: source["x"+j], y: source["y"+j]};
15610                     result = OpenLayers.Geometry.distanceToSegment(p, target);
15611                     if(result.distance < tolerance) {
15612                         if(point) {
15613                             intersection = new OpenLayers.Geometry.Point(p.x, p.y);
15614                         } else {
15615                             intersection = true;
15616                         }
15617                         break outer;
15618                     }
15619                 }
15620             }
15621         }
15622     }
15623     return intersection;
15624 };
15625
15626 /**
15627  * Function: OpenLayers.Geometry.distanceToSegment
15628  *
15629  * Parameters:
15630  * point - {Object} An object with x and y properties representing the
15631  *     point coordinates.
15632  * segment - {Object} An object with x1, y1, x2, and y2 properties
15633  *     representing endpoint coordinates.
15634  *
15635  * Returns:
15636  * {Object} An object with distance, along, x, and y properties.  The distance
15637  *     will be the shortest distance between the input point and segment.
15638  *     The x and y properties represent the coordinates along the segment
15639  *     where the shortest distance meets the segment. The along attribute
15640  *     describes how far between the two segment points the given point is.
15641  */
15642 OpenLayers.Geometry.distanceToSegment = function(point, segment) {
15643     var result = OpenLayers.Geometry.distanceSquaredToSegment(point, segment);
15644     result.distance = Math.sqrt(result.distance);
15645     return result;
15646 };
15647
15648 /**
15649  * Function: OpenLayers.Geometry.distanceSquaredToSegment
15650  *
15651  * Usually the distanceToSegment function should be used. This variant however
15652  * can be used for comparisons where the exact distance is not important.
15653  *
15654  * Parameters:
15655  * point - {Object} An object with x and y properties representing the
15656  *     point coordinates.
15657  * segment - {Object} An object with x1, y1, x2, and y2 properties
15658  *     representing endpoint coordinates.
15659  *
15660  * Returns:
15661  * {Object} An object with squared distance, along, x, and y properties.
15662  *     The distance will be the shortest distance between the input point and
15663  *     segment. The x and y properties represent the coordinates along the
15664  *     segment where the shortest distance meets the segment. The along
15665  *     attribute describes how far between the two segment points the given
15666  *     point is.
15667  */
15668 OpenLayers.Geometry.distanceSquaredToSegment = function(point, segment) {
15669     var x0 = point.x;
15670     var y0 = point.y;
15671     var x1 = segment.x1;
15672     var y1 = segment.y1;
15673     var x2 = segment.x2;
15674     var y2 = segment.y2;
15675     var dx = x2 - x1;
15676     var dy = y2 - y1;
15677     var along = (dx == 0 && dy == 0) ? 0 : ((dx * (x0 - x1)) + (dy * (y0 - y1))) /
15678                 (Math.pow(dx, 2) + Math.pow(dy, 2));
15679     var x, y;
15680     if(along <= 0.0) {
15681         x = x1;
15682         y = y1;
15683     } else if(along >= 1.0) {
15684         x = x2;
15685         y = y2;
15686     } else {
15687         x = x1 + along * dx;
15688         y = y1 + along * dy;
15689     }
15690     return {
15691         distance: Math.pow(x - x0, 2) + Math.pow(y - y0, 2),
15692         x: x, y: y,
15693         along: along
15694     };
15695 };
15696 /* ======================================================================
15697     OpenLayers/Geometry/Point.js
15698    ====================================================================== */
15699
15700 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
15701  * full list of contributors). Published under the 2-clause BSD license.
15702  * See license.txt in the OpenLayers distribution or repository for the
15703  * full text of the license. */
15704
15705 /**
15706  * @requires OpenLayers/Geometry.js
15707  */
15708
15709 /**
15710  * Class: OpenLayers.Geometry.Point
15711  * Point geometry class. 
15712  * 
15713  * Inherits from:
15714  *  - <OpenLayers.Geometry> 
15715  */
15716 OpenLayers.Geometry.Point = OpenLayers.Class(OpenLayers.Geometry, {
15717
15718     /** 
15719      * APIProperty: x 
15720      * {float} 
15721      */
15722     x: null,
15723
15724     /** 
15725      * APIProperty: y 
15726      * {float} 
15727      */
15728     y: null,
15729
15730     /**
15731      * Constructor: OpenLayers.Geometry.Point
15732      * Construct a point geometry.
15733      *
15734      * Parameters:
15735      * x - {float} 
15736      * y - {float}
15737      * 
15738      */
15739     initialize: function(x, y) {
15740         OpenLayers.Geometry.prototype.initialize.apply(this, arguments);
15741         
15742         this.x = parseFloat(x);
15743         this.y = parseFloat(y);
15744     },
15745
15746     /**
15747      * APIMethod: clone
15748      * 
15749      * Returns:
15750      * {<OpenLayers.Geometry.Point>} An exact clone of this OpenLayers.Geometry.Point
15751      */
15752     clone: function(obj) {
15753         if (obj == null) {
15754             obj = new OpenLayers.Geometry.Point(this.x, this.y);
15755         }
15756
15757         // catch any randomly tagged-on properties
15758         OpenLayers.Util.applyDefaults(obj, this);
15759
15760         return obj;
15761     },
15762
15763     /** 
15764      * Method: calculateBounds
15765      * Create a new Bounds based on the lon/lat
15766      */
15767     calculateBounds: function () {
15768         this.bounds = new OpenLayers.Bounds(this.x, this.y,
15769                                             this.x, this.y);
15770     },
15771
15772     /**
15773      * APIMethod: distanceTo
15774      * Calculate the closest distance between two geometries (on the x-y plane).
15775      *
15776      * Parameters:
15777      * geometry - {<OpenLayers.Geometry>} The target geometry.
15778      * options - {Object} Optional properties for configuring the distance
15779      *     calculation.
15780      *
15781      * Valid options:
15782      * details - {Boolean} Return details from the distance calculation.
15783      *     Default is false.
15784      * edge - {Boolean} Calculate the distance from this geometry to the
15785      *     nearest edge of the target geometry.  Default is true.  If true,
15786      *     calling distanceTo from a geometry that is wholly contained within
15787      *     the target will result in a non-zero distance.  If false, whenever
15788      *     geometries intersect, calling distanceTo will return 0.  If false,
15789      *     details cannot be returned.
15790      *
15791      * Returns:
15792      * {Number | Object} The distance between this geometry and the target.
15793      *     If details is true, the return will be an object with distance,
15794      *     x0, y0, x1, and x2 properties.  The x0 and y0 properties represent
15795      *     the coordinates of the closest point on this geometry. The x1 and y1
15796      *     properties represent the coordinates of the closest point on the
15797      *     target geometry.
15798      */
15799     distanceTo: function(geometry, options) {
15800         var edge = !(options && options.edge === false);
15801         var details = edge && options && options.details;
15802         var distance, x0, y0, x1, y1, result;
15803         if(geometry instanceof OpenLayers.Geometry.Point) {
15804             x0 = this.x;
15805             y0 = this.y;
15806             x1 = geometry.x;
15807             y1 = geometry.y;
15808             distance = Math.sqrt(Math.pow(x0 - x1, 2) + Math.pow(y0 - y1, 2));
15809             result = !details ?
15810                 distance : {x0: x0, y0: y0, x1: x1, y1: y1, distance: distance};
15811         } else {
15812             result = geometry.distanceTo(this, options);
15813             if(details) {
15814                 // switch coord order since this geom is target
15815                 result = {
15816                     x0: result.x1, y0: result.y1,
15817                     x1: result.x0, y1: result.y0,
15818                     distance: result.distance
15819                 };
15820             }
15821         }
15822         return result;
15823     },
15824     
15825     /** 
15826      * APIMethod: equals
15827      * Determine whether another geometry is equivalent to this one.  Geometries
15828      *     are considered equivalent if all components have the same coordinates.
15829      * 
15830      * Parameters:
15831      * geom - {<OpenLayers.Geometry.Point>} The geometry to test. 
15832      *
15833      * Returns:
15834      * {Boolean} The supplied geometry is equivalent to this geometry.
15835      */
15836     equals: function(geom) {
15837         var equals = false;
15838         if (geom != null) {
15839             equals = ((this.x == geom.x && this.y == geom.y) ||
15840                       (isNaN(this.x) && isNaN(this.y) && isNaN(geom.x) && isNaN(geom.y)));
15841         }
15842         return equals;
15843     },
15844     
15845     /**
15846      * Method: toShortString
15847      *
15848      * Returns:
15849      * {String} Shortened String representation of Point object. 
15850      *         (ex. <i>"5, 42"</i>)
15851      */
15852     toShortString: function() {
15853         return (this.x + ", " + this.y);
15854     },
15855     
15856     /**
15857      * APIMethod: move
15858      * Moves a geometry by the given displacement along positive x and y axes.
15859      *     This modifies the position of the geometry and clears the cached
15860      *     bounds.
15861      *
15862      * Parameters:
15863      * x - {Float} Distance to move geometry in positive x direction. 
15864      * y - {Float} Distance to move geometry in positive y direction.
15865      */
15866     move: function(x, y) {
15867         this.x = this.x + x;
15868         this.y = this.y + y;
15869         this.clearBounds();
15870     },
15871
15872     /**
15873      * APIMethod: rotate
15874      * Rotate a point around another.
15875      *
15876      * Parameters:
15877      * angle - {Float} Rotation angle in degrees (measured counterclockwise
15878      *                 from the positive x-axis)
15879      * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
15880      */
15881     rotate: function(angle, origin) {
15882         angle *= Math.PI / 180;
15883         var radius = this.distanceTo(origin);
15884         var theta = angle + Math.atan2(this.y - origin.y, this.x - origin.x);
15885         this.x = origin.x + (radius * Math.cos(theta));
15886         this.y = origin.y + (radius * Math.sin(theta));
15887         this.clearBounds();
15888     },
15889     
15890     /**
15891      * APIMethod: getCentroid
15892      *
15893      * Returns:
15894      * {<OpenLayers.Geometry.Point>} The centroid of the collection
15895      */
15896     getCentroid: function() {
15897         return new OpenLayers.Geometry.Point(this.x, this.y);
15898     },
15899
15900     /**
15901      * APIMethod: resize
15902      * Resize a point relative to some origin.  For points, this has the effect
15903      *     of scaling a vector (from the origin to the point).  This method is
15904      *     more useful on geometry collection subclasses.
15905      *
15906      * Parameters:
15907      * scale - {Float} Ratio of the new distance from the origin to the old
15908      *                 distance from the origin.  A scale of 2 doubles the
15909      *                 distance between the point and origin.
15910      * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
15911      * ratio - {Float} Optional x:y ratio for resizing.  Default ratio is 1.
15912      * 
15913      * Returns:
15914      * {<OpenLayers.Geometry>} - The current geometry. 
15915      */
15916     resize: function(scale, origin, ratio) {
15917         ratio = (ratio == undefined) ? 1 : ratio;
15918         this.x = origin.x + (scale * ratio * (this.x - origin.x));
15919         this.y = origin.y + (scale * (this.y - origin.y));
15920         this.clearBounds();
15921         return this;
15922     },
15923     
15924     /**
15925      * APIMethod: intersects
15926      * Determine if the input geometry intersects this one.
15927      *
15928      * Parameters:
15929      * geometry - {<OpenLayers.Geometry>} Any type of geometry.
15930      *
15931      * Returns:
15932      * {Boolean} The input geometry intersects this one.
15933      */
15934     intersects: function(geometry) {
15935         var intersect = false;
15936         if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
15937             intersect = this.equals(geometry);
15938         } else {
15939             intersect = geometry.intersects(this);
15940         }
15941         return intersect;
15942     },
15943     
15944     /**
15945      * APIMethod: transform
15946      * Translate the x,y properties of the point from source to dest.
15947      * 
15948      * Parameters:
15949      * source - {<OpenLayers.Projection>} 
15950      * dest - {<OpenLayers.Projection>}
15951      * 
15952      * Returns:
15953      * {<OpenLayers.Geometry>} 
15954      */
15955     transform: function(source, dest) {
15956         if ((source && dest)) {
15957             OpenLayers.Projection.transform(
15958                 this, source, dest); 
15959             this.bounds = null;
15960         }       
15961         return this;
15962     },
15963
15964     /**
15965      * APIMethod: getVertices
15966      * Return a list of all points in this geometry.
15967      *
15968      * Parameters:
15969      * nodes - {Boolean} For lines, only return vertices that are
15970      *     endpoints.  If false, for lines, only vertices that are not
15971      *     endpoints will be returned.  If not provided, all vertices will
15972      *     be returned.
15973      *
15974      * Returns:
15975      * {Array} A list of all vertices in the geometry.
15976      */
15977     getVertices: function(nodes) {
15978         return [this];
15979     },
15980
15981     CLASS_NAME: "OpenLayers.Geometry.Point"
15982 });
15983 /* ======================================================================
15984     OpenLayers/Geometry/Collection.js
15985    ====================================================================== */
15986
15987 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
15988  * full list of contributors). Published under the 2-clause BSD license.
15989  * See license.txt in the OpenLayers distribution or repository for the
15990  * full text of the license. */
15991
15992 /**
15993  * @requires OpenLayers/Geometry.js
15994  */
15995
15996 /**
15997  * Class: OpenLayers.Geometry.Collection
15998  * A Collection is exactly what it sounds like: A collection of different 
15999  * Geometries. These are stored in the local parameter <components> (which
16000  * can be passed as a parameter to the constructor). 
16001  * 
16002  * As new geometries are added to the collection, they are NOT cloned. 
16003  * When removing geometries, they need to be specified by reference (ie you 
16004  * have to pass in the *exact* geometry to be removed).
16005  * 
16006  * The <getArea> and <getLength> functions here merely iterate through
16007  * the components, summing their respective areas and lengths.
16008  *
16009  * Create a new instance with the <OpenLayers.Geometry.Collection> constructor.
16010  *
16011  * Inherits from:
16012  *  - <OpenLayers.Geometry> 
16013  */
16014 OpenLayers.Geometry.Collection = OpenLayers.Class(OpenLayers.Geometry, {
16015
16016     /**
16017      * APIProperty: components
16018      * {Array(<OpenLayers.Geometry>)} The component parts of this geometry
16019      */
16020     components: null,
16021     
16022     /**
16023      * Property: componentTypes
16024      * {Array(String)} An array of class names representing the types of
16025      * components that the collection can include.  A null value means the
16026      * component types are not restricted.
16027      */
16028     componentTypes: null,
16029
16030     /**
16031      * Constructor: OpenLayers.Geometry.Collection
16032      * Creates a Geometry Collection -- a list of geoms.
16033      *
16034      * Parameters: 
16035      * components - {Array(<OpenLayers.Geometry>)} Optional array of geometries
16036      *
16037      */
16038     initialize: function (components) {
16039         OpenLayers.Geometry.prototype.initialize.apply(this, arguments);
16040         this.components = [];
16041         if (components != null) {
16042             this.addComponents(components);
16043         }
16044     },
16045
16046     /**
16047      * APIMethod: destroy
16048      * Destroy this geometry.
16049      */
16050     destroy: function () {
16051         this.components.length = 0;
16052         this.components = null;
16053         OpenLayers.Geometry.prototype.destroy.apply(this, arguments);
16054     },
16055
16056     /**
16057      * APIMethod: clone
16058      * Clone this geometry.
16059      *
16060      * Returns:
16061      * {<OpenLayers.Geometry.Collection>} An exact clone of this collection
16062      */
16063     clone: function() {
16064         var Constructor = OpenLayers.Util.getConstructor(this.CLASS_NAME);
16065         var geometry = new Constructor();
16066         for(var i=0, len=this.components.length; i<len; i++) {
16067             geometry.addComponent(this.components[i].clone());
16068         }
16069         
16070         // catch any randomly tagged-on properties
16071         OpenLayers.Util.applyDefaults(geometry, this);
16072         
16073         return geometry;
16074     },
16075
16076     /**
16077      * Method: getComponentsString
16078      * Get a string representing the components for this collection
16079      * 
16080      * Returns:
16081      * {String} A string representation of the components of this geometry
16082      */
16083     getComponentsString: function(){
16084         var strings = [];
16085         for(var i=0, len=this.components.length; i<len; i++) {
16086             strings.push(this.components[i].toShortString()); 
16087         }
16088         return strings.join(",");
16089     },
16090
16091     /**
16092      * APIMethod: calculateBounds
16093      * Recalculate the bounds by iterating through the components and 
16094      * calling calling extendBounds() on each item.
16095      */
16096     calculateBounds: function() {
16097         this.bounds = null;
16098         var bounds = new OpenLayers.Bounds();
16099         var components = this.components;
16100         if (components) {
16101             for (var i=0, len=components.length; i<len; i++) {
16102                 bounds.extend(components[i].getBounds());
16103             }
16104         }
16105         // to preserve old behavior, we only set bounds if non-null
16106         // in the future, we could add bounds.isEmpty()
16107         if (bounds.left != null && bounds.bottom != null && 
16108             bounds.right != null && bounds.top != null) {
16109             this.setBounds(bounds);
16110         }
16111     },
16112
16113     /**
16114      * APIMethod: addComponents
16115      * Add components to this geometry.
16116      *
16117      * Parameters:
16118      * components - {Array(<OpenLayers.Geometry>)} An array of geometries to add
16119      */
16120     addComponents: function(components){
16121         if(!(OpenLayers.Util.isArray(components))) {
16122             components = [components];
16123         }
16124         for(var i=0, len=components.length; i<len; i++) {
16125             this.addComponent(components[i]);
16126         }
16127     },
16128
16129     /**
16130      * Method: addComponent
16131      * Add a new component (geometry) to the collection.  If this.componentTypes
16132      * is set, then the component class name must be in the componentTypes array.
16133      *
16134      * The bounds cache is reset.
16135      * 
16136      * Parameters:
16137      * component - {<OpenLayers.Geometry>} A geometry to add
16138      * index - {int} Optional index into the array to insert the component
16139      *
16140      * Returns:
16141      * {Boolean} The component geometry was successfully added
16142      */    
16143     addComponent: function(component, index) {
16144         var added = false;
16145         if(component) {
16146             if(this.componentTypes == null ||
16147                (OpenLayers.Util.indexOf(this.componentTypes,
16148                                         component.CLASS_NAME) > -1)) {
16149
16150                 if(index != null && (index < this.components.length)) {
16151                     var components1 = this.components.slice(0, index);
16152                     var components2 = this.components.slice(index, 
16153                                                            this.components.length);
16154                     components1.push(component);
16155                     this.components = components1.concat(components2);
16156                 } else {
16157                     this.components.push(component);
16158                 }
16159                 component.parent = this;
16160                 this.clearBounds();
16161                 added = true;
16162             }
16163         }
16164         return added;
16165     },
16166     
16167     /**
16168      * APIMethod: removeComponents
16169      * Remove components from this geometry.
16170      *
16171      * Parameters:
16172      * components - {Array(<OpenLayers.Geometry>)} The components to be removed
16173      *
16174      * Returns: 
16175      * {Boolean} A component was removed.
16176      */
16177     removeComponents: function(components) {
16178         var removed = false;
16179
16180         if(!(OpenLayers.Util.isArray(components))) {
16181             components = [components];
16182         }
16183         for(var i=components.length-1; i>=0; --i) {
16184             removed = this.removeComponent(components[i]) || removed;
16185         }
16186         return removed;
16187     },
16188     
16189     /**
16190      * Method: removeComponent
16191      * Remove a component from this geometry.
16192      *
16193      * Parameters:
16194      * component - {<OpenLayers.Geometry>} 
16195      *
16196      * Returns: 
16197      * {Boolean} The component was removed.
16198      */
16199     removeComponent: function(component) {
16200         
16201         OpenLayers.Util.removeItem(this.components, component);
16202         
16203         // clearBounds() so that it gets recalculated on the next call
16204         // to this.getBounds();
16205         this.clearBounds();
16206         return true;
16207     },
16208
16209     /**
16210      * APIMethod: getLength
16211      * Calculate the length of this geometry
16212      *
16213      * Returns:
16214      * {Float} The length of the geometry
16215      */
16216     getLength: function() {
16217         var length = 0.0;
16218         for (var i=0, len=this.components.length; i<len; i++) {
16219             length += this.components[i].getLength();
16220         }
16221         return length;
16222     },
16223     
16224     /**
16225      * APIMethod: getArea
16226      * Calculate the area of this geometry. Note how this function is overridden
16227      * in <OpenLayers.Geometry.Polygon>.
16228      *
16229      * Returns:
16230      * {Float} The area of the collection by summing its parts
16231      */
16232     getArea: function() {
16233         var area = 0.0;
16234         for (var i=0, len=this.components.length; i<len; i++) {
16235             area += this.components[i].getArea();
16236         }
16237         return area;
16238     },
16239
16240     /** 
16241      * APIMethod: getGeodesicArea
16242      * Calculate the approximate area of the polygon were it projected onto
16243      *     the earth.
16244      *
16245      * Parameters:
16246      * projection - {<OpenLayers.Projection>} The spatial reference system
16247      *     for the geometry coordinates.  If not provided, Geographic/WGS84 is
16248      *     assumed.
16249      * 
16250      * Reference:
16251      * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
16252      *     Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
16253      *     Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
16254      *
16255      * Returns:
16256      * {float} The approximate geodesic area of the geometry in square meters.
16257      */
16258     getGeodesicArea: function(projection) {
16259         var area = 0.0;
16260         for(var i=0, len=this.components.length; i<len; i++) {
16261             area += this.components[i].getGeodesicArea(projection);
16262         }
16263         return area;
16264     },
16265     
16266     /**
16267      * APIMethod: getCentroid
16268      *
16269      * Compute the centroid for this geometry collection.
16270      *
16271      * Parameters:
16272      * weighted - {Boolean} Perform the getCentroid computation recursively,
16273      * returning an area weighted average of all geometries in this collection.
16274      *
16275      * Returns:
16276      * {<OpenLayers.Geometry.Point>} The centroid of the collection
16277      */
16278     getCentroid: function(weighted) {
16279         if (!weighted) {
16280             return this.components.length && this.components[0].getCentroid();
16281         }
16282         var len = this.components.length;
16283         if (!len) {
16284             return false;
16285         }
16286         
16287         var areas = [];
16288         var centroids = [];
16289         var areaSum = 0;
16290         var minArea = Number.MAX_VALUE;
16291         var component;
16292         for (var i=0; i<len; ++i) {
16293             component = this.components[i];
16294             var area = component.getArea();
16295             var centroid = component.getCentroid(true);
16296             if (isNaN(area) || isNaN(centroid.x) || isNaN(centroid.y)) {
16297                 continue;
16298             }
16299             areas.push(area);
16300             areaSum += area;
16301             minArea = (area < minArea && area > 0) ? area : minArea;
16302             centroids.push(centroid);
16303         }
16304         len = areas.length;
16305         if (areaSum === 0) {
16306             // all the components in this collection have 0 area
16307             // probably a collection of points -- weight all the points the same
16308             for (var i=0; i<len; ++i) {
16309                 areas[i] = 1;
16310             }
16311             areaSum = areas.length;
16312         } else {
16313             // normalize all the areas where the smallest area will get
16314             // a value of 1
16315             for (var i=0; i<len; ++i) {
16316                 areas[i] /= minArea;
16317             }
16318             areaSum /= minArea;
16319         }
16320         
16321         var xSum = 0, ySum = 0, centroid, area;
16322         for (var i=0; i<len; ++i) {
16323             centroid = centroids[i];
16324             area = areas[i];
16325             xSum += centroid.x * area;
16326             ySum += centroid.y * area;
16327         }
16328         
16329         return new OpenLayers.Geometry.Point(xSum/areaSum, ySum/areaSum);
16330     },
16331
16332     /**
16333      * APIMethod: getGeodesicLength
16334      * Calculate the approximate length of the geometry were it projected onto
16335      *     the earth.
16336      *
16337      * projection - {<OpenLayers.Projection>} The spatial reference system
16338      *     for the geometry coordinates.  If not provided, Geographic/WGS84 is
16339      *     assumed.
16340      * 
16341      * Returns:
16342      * {Float} The appoximate geodesic length of the geometry in meters.
16343      */
16344     getGeodesicLength: function(projection) {
16345         var length = 0.0;
16346         for(var i=0, len=this.components.length; i<len; i++) {
16347             length += this.components[i].getGeodesicLength(projection);
16348         }
16349         return length;
16350     },
16351
16352     /**
16353      * APIMethod: move
16354      * Moves a geometry by the given displacement along positive x and y axes.
16355      *     This modifies the position of the geometry and clears the cached
16356      *     bounds.
16357      *
16358      * Parameters:
16359      * x - {Float} Distance to move geometry in positive x direction. 
16360      * y - {Float} Distance to move geometry in positive y direction.
16361      */
16362     move: function(x, y) {
16363         for(var i=0, len=this.components.length; i<len; i++) {
16364             this.components[i].move(x, y);
16365         }
16366     },
16367
16368     /**
16369      * APIMethod: rotate
16370      * Rotate a geometry around some origin
16371      *
16372      * Parameters:
16373      * angle - {Float} Rotation angle in degrees (measured counterclockwise
16374      *                 from the positive x-axis)
16375      * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
16376      */
16377     rotate: function(angle, origin) {
16378         for(var i=0, len=this.components.length; i<len; ++i) {
16379             this.components[i].rotate(angle, origin);
16380         }
16381     },
16382
16383     /**
16384      * APIMethod: resize
16385      * Resize a geometry relative to some origin.  Use this method to apply
16386      *     a uniform scaling to a geometry.
16387      *
16388      * Parameters:
16389      * scale - {Float} Factor by which to scale the geometry.  A scale of 2
16390      *                 doubles the size of the geometry in each dimension
16391      *                 (lines, for example, will be twice as long, and polygons
16392      *                 will have four times the area).
16393      * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
16394      * ratio - {Float} Optional x:y ratio for resizing.  Default ratio is 1.
16395      * 
16396      * Returns:
16397      * {<OpenLayers.Geometry>} - The current geometry. 
16398      */
16399     resize: function(scale, origin, ratio) {
16400         for(var i=0; i<this.components.length; ++i) {
16401             this.components[i].resize(scale, origin, ratio);
16402         }
16403         return this;
16404     },
16405
16406     /**
16407      * APIMethod: distanceTo
16408      * Calculate the closest distance between two geometries (on the x-y plane).
16409      *
16410      * Parameters:
16411      * geometry - {<OpenLayers.Geometry>} The target geometry.
16412      * options - {Object} Optional properties for configuring the distance
16413      *     calculation.
16414      *
16415      * Valid options:
16416      * details - {Boolean} Return details from the distance calculation.
16417      *     Default is false.
16418      * edge - {Boolean} Calculate the distance from this geometry to the
16419      *     nearest edge of the target geometry.  Default is true.  If true,
16420      *     calling distanceTo from a geometry that is wholly contained within
16421      *     the target will result in a non-zero distance.  If false, whenever
16422      *     geometries intersect, calling distanceTo will return 0.  If false,
16423      *     details cannot be returned.
16424      *
16425      * Returns:
16426      * {Number | Object} The distance between this geometry and the target.
16427      *     If details is true, the return will be an object with distance,
16428      *     x0, y0, x1, and y1 properties.  The x0 and y0 properties represent
16429      *     the coordinates of the closest point on this geometry. The x1 and y1
16430      *     properties represent the coordinates of the closest point on the
16431      *     target geometry.
16432      */
16433     distanceTo: function(geometry, options) {
16434         var edge = !(options && options.edge === false);
16435         var details = edge && options && options.details;
16436         var result, best, distance;
16437         var min = Number.POSITIVE_INFINITY;
16438         for(var i=0, len=this.components.length; i<len; ++i) {
16439             result = this.components[i].distanceTo(geometry, options);
16440             distance = details ? result.distance : result;
16441             if(distance < min) {
16442                 min = distance;
16443                 best = result;
16444                 if(min == 0) {
16445                     break;
16446                 }
16447             }
16448         }
16449         return best;
16450     },
16451
16452     /** 
16453      * APIMethod: equals
16454      * Determine whether another geometry is equivalent to this one.  Geometries
16455      *     are considered equivalent if all components have the same coordinates.
16456      * 
16457      * Parameters:
16458      * geometry - {<OpenLayers.Geometry>} The geometry to test. 
16459      *
16460      * Returns:
16461      * {Boolean} The supplied geometry is equivalent to this geometry.
16462      */
16463     equals: function(geometry) {
16464         var equivalent = true;
16465         if(!geometry || !geometry.CLASS_NAME ||
16466            (this.CLASS_NAME != geometry.CLASS_NAME)) {
16467             equivalent = false;
16468         } else if(!(OpenLayers.Util.isArray(geometry.components)) ||
16469                   (geometry.components.length != this.components.length)) {
16470             equivalent = false;
16471         } else {
16472             for(var i=0, len=this.components.length; i<len; ++i) {
16473                 if(!this.components[i].equals(geometry.components[i])) {
16474                     equivalent = false;
16475                     break;
16476                 }
16477             }
16478         }
16479         return equivalent;
16480     },
16481
16482     /**
16483      * APIMethod: transform
16484      * Reproject the components geometry from source to dest.
16485      * 
16486      * Parameters:
16487      * source - {<OpenLayers.Projection>} 
16488      * dest - {<OpenLayers.Projection>}
16489      * 
16490      * Returns:
16491      * {<OpenLayers.Geometry>} 
16492      */
16493     transform: function(source, dest) {
16494         if (source && dest) {
16495             for (var i=0, len=this.components.length; i<len; i++) {  
16496                 var component = this.components[i];
16497                 component.transform(source, dest);
16498             }
16499             this.bounds = null;
16500         }
16501         return this;
16502     },
16503
16504     /**
16505      * APIMethod: intersects
16506      * Determine if the input geometry intersects this one.
16507      *
16508      * Parameters:
16509      * geometry - {<OpenLayers.Geometry>} Any type of geometry.
16510      *
16511      * Returns:
16512      * {Boolean} The input geometry intersects this one.
16513      */
16514     intersects: function(geometry) {
16515         var intersect = false;
16516         for(var i=0, len=this.components.length; i<len; ++ i) {
16517             intersect = geometry.intersects(this.components[i]);
16518             if(intersect) {
16519                 break;
16520             }
16521         }
16522         return intersect;
16523     },
16524
16525     /**
16526      * APIMethod: getVertices
16527      * Return a list of all points in this geometry.
16528      *
16529      * Parameters:
16530      * nodes - {Boolean} For lines, only return vertices that are
16531      *     endpoints.  If false, for lines, only vertices that are not
16532      *     endpoints will be returned.  If not provided, all vertices will
16533      *     be returned.
16534      *
16535      * Returns:
16536      * {Array} A list of all vertices in the geometry.
16537      */
16538     getVertices: function(nodes) {
16539         var vertices = [];
16540         for(var i=0, len=this.components.length; i<len; ++i) {
16541             Array.prototype.push.apply(
16542                 vertices, this.components[i].getVertices(nodes)
16543             );
16544         }
16545         return vertices;
16546     },
16547
16548
16549     CLASS_NAME: "OpenLayers.Geometry.Collection"
16550 });
16551 /* ======================================================================
16552     OpenLayers/Geometry/MultiPoint.js
16553    ====================================================================== */
16554
16555 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
16556  * full list of contributors). Published under the 2-clause BSD license.
16557  * See license.txt in the OpenLayers distribution or repository for the
16558  * full text of the license. */
16559
16560 /**
16561  * @requires OpenLayers/Geometry/Collection.js
16562  * @requires OpenLayers/Geometry/Point.js
16563  */
16564
16565 /**
16566  * Class: OpenLayers.Geometry.MultiPoint
16567  * MultiPoint is a collection of Points.  Create a new instance with the
16568  * <OpenLayers.Geometry.MultiPoint> constructor.
16569  *
16570  * Inherits from:
16571  *  - <OpenLayers.Geometry.Collection>
16572  *  - <OpenLayers.Geometry>
16573  */
16574 OpenLayers.Geometry.MultiPoint = OpenLayers.Class(
16575   OpenLayers.Geometry.Collection, {
16576
16577     /**
16578      * Property: componentTypes
16579      * {Array(String)} An array of class names representing the types of
16580      * components that the collection can include.  A null value means the
16581      * component types are not restricted.
16582      */
16583     componentTypes: ["OpenLayers.Geometry.Point"],
16584
16585     /**
16586      * Constructor: OpenLayers.Geometry.MultiPoint
16587      * Create a new MultiPoint Geometry
16588      *
16589      * Parameters:
16590      * components - {Array(<OpenLayers.Geometry.Point>)} 
16591      *
16592      * Returns:
16593      * {<OpenLayers.Geometry.MultiPoint>}
16594      */
16595
16596     /**
16597      * APIMethod: addPoint
16598      * Wrapper for <OpenLayers.Geometry.Collection.addComponent>
16599      *
16600      * Parameters:
16601      * point - {<OpenLayers.Geometry.Point>} Point to be added
16602      * index - {Integer} Optional index
16603      */
16604     addPoint: function(point, index) {
16605         this.addComponent(point, index);
16606     },
16607     
16608     /**
16609      * APIMethod: removePoint
16610      * Wrapper for <OpenLayers.Geometry.Collection.removeComponent>
16611      *
16612      * Parameters:
16613      * point - {<OpenLayers.Geometry.Point>} Point to be removed
16614      */
16615     removePoint: function(point){
16616         this.removeComponent(point);
16617     },
16618
16619     CLASS_NAME: "OpenLayers.Geometry.MultiPoint"
16620 });
16621 /* ======================================================================
16622     OpenLayers/Geometry/Curve.js
16623    ====================================================================== */
16624
16625 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
16626  * full list of contributors). Published under the 2-clause BSD license.
16627  * See license.txt in the OpenLayers distribution or repository for the
16628  * full text of the license. */
16629
16630 /**
16631  * @requires OpenLayers/Geometry/MultiPoint.js
16632  */
16633
16634 /**
16635  * Class: OpenLayers.Geometry.Curve
16636  * A Curve is a MultiPoint, whose points are assumed to be connected. To 
16637  * this end, we provide a "getLength()" function, which iterates through 
16638  * the points, summing the distances between them. 
16639  * 
16640  * Inherits: 
16641  *  - <OpenLayers.Geometry.MultiPoint>
16642  */
16643 OpenLayers.Geometry.Curve = OpenLayers.Class(OpenLayers.Geometry.MultiPoint, {
16644
16645     /**
16646      * Property: componentTypes
16647      * {Array(String)} An array of class names representing the types of 
16648      *                 components that the collection can include.  A null 
16649      *                 value means the component types are not restricted.
16650      */
16651     componentTypes: ["OpenLayers.Geometry.Point"],
16652
16653     /**
16654      * Constructor: OpenLayers.Geometry.Curve
16655      * 
16656      * Parameters:
16657      * point - {Array(<OpenLayers.Geometry.Point>)}
16658      */
16659     
16660     /**
16661      * APIMethod: getLength
16662      * 
16663      * Returns:
16664      * {Float} The length of the curve
16665      */
16666     getLength: function() {
16667         var length = 0.0;
16668         if ( this.components && (this.components.length > 1)) {
16669             for(var i=1, len=this.components.length; i<len; i++) {
16670                 length += this.components[i-1].distanceTo(this.components[i]);
16671             }
16672         }
16673         return length;
16674     },
16675
16676     /**
16677      * APIMethod: getGeodesicLength
16678      * Calculate the approximate length of the geometry were it projected onto
16679      *     the earth.
16680      *
16681      * projection - {<OpenLayers.Projection>} The spatial reference system
16682      *     for the geometry coordinates.  If not provided, Geographic/WGS84 is
16683      *     assumed.
16684      * 
16685      * Returns:
16686      * {Float} The appoximate geodesic length of the geometry in meters.
16687      */
16688     getGeodesicLength: function(projection) {
16689         var geom = this;  // so we can work with a clone if needed
16690         if(projection) {
16691             var gg = new OpenLayers.Projection("EPSG:4326");
16692             if(!gg.equals(projection)) {
16693                 geom = this.clone().transform(projection, gg);
16694             }
16695         }
16696         var length = 0.0;
16697         if(geom.components && (geom.components.length > 1)) {
16698             var p1, p2;
16699             for(var i=1, len=geom.components.length; i<len; i++) {
16700                 p1 = geom.components[i-1];
16701                 p2 = geom.components[i];
16702                 // this returns km and requires lon/lat properties
16703                 length += OpenLayers.Util.distVincenty(
16704                     {lon: p1.x, lat: p1.y}, {lon: p2.x, lat: p2.y}
16705                 );
16706             }
16707         }
16708         // convert to m
16709         return length * 1000;
16710     },
16711
16712     CLASS_NAME: "OpenLayers.Geometry.Curve"
16713 });
16714 /* ======================================================================
16715     OpenLayers/Geometry/LineString.js
16716    ====================================================================== */
16717
16718 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
16719  * full list of contributors). Published under the 2-clause BSD license.
16720  * See license.txt in the OpenLayers distribution or repository for the
16721  * full text of the license. */
16722
16723 /**
16724  * @requires OpenLayers/Geometry/Curve.js
16725  */
16726
16727 /**
16728  * Class: OpenLayers.Geometry.LineString
16729  * A LineString is a Curve which, once two points have been added to it, can 
16730  * never be less than two points long.
16731  * 
16732  * Inherits from:
16733  *  - <OpenLayers.Geometry.Curve>
16734  */
16735 OpenLayers.Geometry.LineString = OpenLayers.Class(OpenLayers.Geometry.Curve, {
16736
16737     /**
16738      * Constructor: OpenLayers.Geometry.LineString
16739      * Create a new LineString geometry
16740      *
16741      * Parameters:
16742      * points - {Array(<OpenLayers.Geometry.Point>)} An array of points used to
16743      *          generate the linestring
16744      *
16745      */
16746
16747     /**
16748      * APIMethod: removeComponent
16749      * Only allows removal of a point if there are three or more points in 
16750      * the linestring. (otherwise the result would be just a single point)
16751      *
16752      * Parameters: 
16753      * point - {<OpenLayers.Geometry.Point>} The point to be removed
16754      *
16755      * Returns: 
16756      * {Boolean} The component was removed.
16757      */
16758     removeComponent: function(point) {
16759         var removed = this.components && (this.components.length > 2);
16760         if (removed) {
16761             OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this, 
16762                                                                   arguments);
16763         }
16764         return removed;
16765     },
16766     
16767     /**
16768      * APIMethod: intersects
16769      * Test for instersection between two geometries.  This is a cheapo
16770      *     implementation of the Bently-Ottmann algorigithm.  It doesn't
16771      *     really keep track of a sweep line data structure.  It is closer
16772      *     to the brute force method, except that segments are sorted and
16773      *     potential intersections are only calculated when bounding boxes
16774      *     intersect.
16775      *
16776      * Parameters:
16777      * geometry - {<OpenLayers.Geometry>}
16778      *
16779      * Returns:
16780      * {Boolean} The input geometry intersects this geometry.
16781      */
16782     intersects: function(geometry) {
16783         var intersect = false;
16784         var type = geometry.CLASS_NAME;
16785         if(type == "OpenLayers.Geometry.LineString" ||
16786            type == "OpenLayers.Geometry.LinearRing" ||
16787            type == "OpenLayers.Geometry.Point") {
16788             var segs1 = this.getSortedSegments();
16789             var segs2;
16790             if(type == "OpenLayers.Geometry.Point") {
16791                 segs2 = [{
16792                     x1: geometry.x, y1: geometry.y,
16793                     x2: geometry.x, y2: geometry.y
16794                 }];
16795             } else {
16796                 segs2 = geometry.getSortedSegments();
16797             }
16798             var seg1, seg1x1, seg1x2, seg1y1, seg1y2,
16799                 seg2, seg2y1, seg2y2;
16800             // sweep right
16801             outer: for(var i=0, len=segs1.length; i<len; ++i) {
16802                 seg1 = segs1[i];
16803                 seg1x1 = seg1.x1;
16804                 seg1x2 = seg1.x2;
16805                 seg1y1 = seg1.y1;
16806                 seg1y2 = seg1.y2;
16807                 inner: for(var j=0, jlen=segs2.length; j<jlen; ++j) {
16808                     seg2 = segs2[j];
16809                     if(seg2.x1 > seg1x2) {
16810                         // seg1 still left of seg2
16811                         break;
16812                     }
16813                     if(seg2.x2 < seg1x1) {
16814                         // seg2 still left of seg1
16815                         continue;
16816                     }
16817                     seg2y1 = seg2.y1;
16818                     seg2y2 = seg2.y2;
16819                     if(Math.min(seg2y1, seg2y2) > Math.max(seg1y1, seg1y2)) {
16820                         // seg2 above seg1
16821                         continue;
16822                     }
16823                     if(Math.max(seg2y1, seg2y2) < Math.min(seg1y1, seg1y2)) {
16824                         // seg2 below seg1
16825                         continue;
16826                     }
16827                     if(OpenLayers.Geometry.segmentsIntersect(seg1, seg2)) {
16828                         intersect = true;
16829                         break outer;
16830                     }
16831                 }
16832             }
16833         } else {
16834             intersect = geometry.intersects(this);
16835         }
16836         return intersect;
16837     },
16838     
16839     /**
16840      * Method: getSortedSegments
16841      *
16842      * Returns:
16843      * {Array} An array of segment objects.  Segment objects have properties
16844      *     x1, y1, x2, and y2.  The start point is represented by x1 and y1.
16845      *     The end point is represented by x2 and y2.  Start and end are
16846      *     ordered so that x1 < x2.
16847      */
16848     getSortedSegments: function() {
16849         var numSeg = this.components.length - 1;
16850         var segments = new Array(numSeg), point1, point2;
16851         for(var i=0; i<numSeg; ++i) {
16852             point1 = this.components[i];
16853             point2 = this.components[i + 1];
16854             if(point1.x < point2.x) {
16855                 segments[i] = {
16856                     x1: point1.x,
16857                     y1: point1.y,
16858                     x2: point2.x,
16859                     y2: point2.y
16860                 };
16861             } else {
16862                 segments[i] = {
16863                     x1: point2.x,
16864                     y1: point2.y,
16865                     x2: point1.x,
16866                     y2: point1.y
16867                 };
16868             }
16869         }
16870         // more efficient to define this somewhere static
16871         function byX1(seg1, seg2) {
16872             return seg1.x1 - seg2.x1;
16873         }
16874         return segments.sort(byX1);
16875     },
16876     
16877     /**
16878      * Method: splitWithSegment
16879      * Split this geometry with the given segment.
16880      *
16881      * Parameters:
16882      * seg - {Object} An object with x1, y1, x2, and y2 properties referencing
16883      *     segment endpoint coordinates.
16884      * options - {Object} Properties of this object will be used to determine
16885      *     how the split is conducted.
16886      *
16887      * Valid options:
16888      * edge - {Boolean} Allow splitting when only edges intersect.  Default is
16889      *     true.  If false, a vertex on the source segment must be within the
16890      *     tolerance distance of the intersection to be considered a split.
16891      * tolerance - {Number} If a non-null value is provided, intersections
16892      *     within the tolerance distance of one of the source segment's
16893      *     endpoints will be assumed to occur at the endpoint.
16894      *
16895      * Returns:
16896      * {Object} An object with *lines* and *points* properties.  If the given
16897      *     segment intersects this linestring, the lines array will reference
16898      *     geometries that result from the split.  The points array will contain
16899      *     all intersection points.  Intersection points are sorted along the
16900      *     segment (in order from x1,y1 to x2,y2).
16901      */
16902     splitWithSegment: function(seg, options) {
16903         var edge = !(options && options.edge === false);
16904         var tolerance = options && options.tolerance;
16905         var lines = [];
16906         var verts = this.getVertices();
16907         var points = [];
16908         var intersections = [];
16909         var split = false;
16910         var vert1, vert2, point;
16911         var node, vertex, target;
16912         var interOptions = {point: true, tolerance: tolerance};
16913         var result = null;
16914         for(var i=0, stop=verts.length-2; i<=stop; ++i) {
16915             vert1 = verts[i];
16916             points.push(vert1.clone());
16917             vert2 = verts[i+1];
16918             target = {x1: vert1.x, y1: vert1.y, x2: vert2.x, y2: vert2.y};
16919             point = OpenLayers.Geometry.segmentsIntersect(
16920                 seg, target, interOptions
16921             );
16922             if(point instanceof OpenLayers.Geometry.Point) {
16923                 if((point.x === seg.x1 && point.y === seg.y1) ||
16924                    (point.x === seg.x2 && point.y === seg.y2) ||
16925                    point.equals(vert1) || point.equals(vert2)) {
16926                     vertex = true;
16927                 } else {
16928                     vertex = false;
16929                 }
16930                 if(vertex || edge) {
16931                     // push intersections different than the previous
16932                     if(!point.equals(intersections[intersections.length-1])) {
16933                         intersections.push(point.clone());
16934                     }
16935                     if(i === 0) {
16936                         if(point.equals(vert1)) {
16937                             continue;
16938                         }
16939                     }
16940                     if(point.equals(vert2)) {
16941                         continue;
16942                     }
16943                     split = true;
16944                     if(!point.equals(vert1)) {
16945                         points.push(point);
16946                     }
16947                     lines.push(new OpenLayers.Geometry.LineString(points));
16948                     points = [point.clone()];
16949                 }
16950             }
16951         }
16952         if(split) {
16953             points.push(vert2.clone());
16954             lines.push(new OpenLayers.Geometry.LineString(points));
16955         }
16956         if(intersections.length > 0) {
16957             // sort intersections along segment
16958             var xDir = seg.x1 < seg.x2 ? 1 : -1;
16959             var yDir = seg.y1 < seg.y2 ? 1 : -1;
16960             result = {
16961                 lines: lines,
16962                 points: intersections.sort(function(p1, p2) {
16963                     return (xDir * p1.x - xDir * p2.x) || (yDir * p1.y - yDir * p2.y);
16964                 })
16965             };
16966         }
16967         return result;
16968     },
16969
16970     /**
16971      * Method: split
16972      * Use this geometry (the source) to attempt to split a target geometry.
16973      * 
16974      * Parameters:
16975      * target - {<OpenLayers.Geometry>} The target geometry.
16976      * options - {Object} Properties of this object will be used to determine
16977      *     how the split is conducted.
16978      *
16979      * Valid options:
16980      * mutual - {Boolean} Split the source geometry in addition to the target
16981      *     geometry.  Default is false.
16982      * edge - {Boolean} Allow splitting when only edges intersect.  Default is
16983      *     true.  If false, a vertex on the source must be within the tolerance
16984      *     distance of the intersection to be considered a split.
16985      * tolerance - {Number} If a non-null value is provided, intersections
16986      *     within the tolerance distance of an existing vertex on the source
16987      *     will be assumed to occur at the vertex.
16988      * 
16989      * Returns:
16990      * {Array} A list of geometries (of this same type as the target) that
16991      *     result from splitting the target with the source geometry.  The
16992      *     source and target geometry will remain unmodified.  If no split
16993      *     results, null will be returned.  If mutual is true and a split
16994      *     results, return will be an array of two arrays - the first will be
16995      *     all geometries that result from splitting the source geometry and
16996      *     the second will be all geometries that result from splitting the
16997      *     target geometry.
16998      */
16999     split: function(target, options) {
17000         var results = null;
17001         var mutual = options && options.mutual;
17002         var sourceSplit, targetSplit, sourceParts, targetParts;
17003         if(target instanceof OpenLayers.Geometry.LineString) {
17004             var verts = this.getVertices();
17005             var vert1, vert2, seg, splits, lines, point;
17006             var points = [];
17007             sourceParts = [];
17008             for(var i=0, stop=verts.length-2; i<=stop; ++i) {
17009                 vert1 = verts[i];
17010                 vert2 = verts[i+1];
17011                 seg = {
17012                     x1: vert1.x, y1: vert1.y,
17013                     x2: vert2.x, y2: vert2.y
17014                 };
17015                 targetParts = targetParts || [target];
17016                 if(mutual) {
17017                     points.push(vert1.clone());
17018                 }
17019                 for(var j=0; j<targetParts.length; ++j) {
17020                     splits = targetParts[j].splitWithSegment(seg, options);
17021                     if(splits) {
17022                         // splice in new features
17023                         lines = splits.lines;
17024                         if(lines.length > 0) {
17025                             lines.unshift(j, 1);
17026                             Array.prototype.splice.apply(targetParts, lines);
17027                             j += lines.length - 2;
17028                         }
17029                         if(mutual) {
17030                             for(var k=0, len=splits.points.length; k<len; ++k) {
17031                                 point = splits.points[k];
17032                                 if(!point.equals(vert1)) {
17033                                     points.push(point);
17034                                     sourceParts.push(new OpenLayers.Geometry.LineString(points));
17035                                     if(point.equals(vert2)) {
17036                                         points = [];
17037                                     } else {
17038                                         points = [point.clone()];
17039                                     }
17040                                 }
17041                             }
17042                         }
17043                     }
17044                 }
17045             }
17046             if(mutual && sourceParts.length > 0 && points.length > 0) {
17047                 points.push(vert2.clone());
17048                 sourceParts.push(new OpenLayers.Geometry.LineString(points));
17049             }
17050         } else {
17051             results = target.splitWith(this, options);
17052         }
17053         if(targetParts && targetParts.length > 1) {
17054             targetSplit = true;
17055         } else {
17056             targetParts = [];
17057         }
17058         if(sourceParts && sourceParts.length > 1) {
17059             sourceSplit = true;
17060         } else {
17061             sourceParts = [];
17062         }
17063         if(targetSplit || sourceSplit) {
17064             if(mutual) {
17065                 results = [sourceParts, targetParts];
17066             } else {
17067                 results = targetParts;
17068             }
17069         }
17070         return results;
17071     },
17072
17073     /**
17074      * Method: splitWith
17075      * Split this geometry (the target) with the given geometry (the source).
17076      *
17077      * Parameters:
17078      * geometry - {<OpenLayers.Geometry>} A geometry used to split this
17079      *     geometry (the source).
17080      * options - {Object} Properties of this object will be used to determine
17081      *     how the split is conducted.
17082      *
17083      * Valid options:
17084      * mutual - {Boolean} Split the source geometry in addition to the target
17085      *     geometry.  Default is false.
17086      * edge - {Boolean} Allow splitting when only edges intersect.  Default is
17087      *     true.  If false, a vertex on the source must be within the tolerance
17088      *     distance of the intersection to be considered a split.
17089      * tolerance - {Number} If a non-null value is provided, intersections
17090      *     within the tolerance distance of an existing vertex on the source
17091      *     will be assumed to occur at the vertex.
17092      * 
17093      * Returns:
17094      * {Array} A list of geometries (of this same type as the target) that
17095      *     result from splitting the target with the source geometry.  The
17096      *     source and target geometry will remain unmodified.  If no split
17097      *     results, null will be returned.  If mutual is true and a split
17098      *     results, return will be an array of two arrays - the first will be
17099      *     all geometries that result from splitting the source geometry and
17100      *     the second will be all geometries that result from splitting the
17101      *     target geometry.
17102      */
17103     splitWith: function(geometry, options) {
17104         return geometry.split(this, options);
17105
17106     },
17107
17108     /**
17109      * APIMethod: getVertices
17110      * Return a list of all points in this geometry.
17111      *
17112      * Parameters:
17113      * nodes - {Boolean} For lines, only return vertices that are
17114      *     endpoints.  If false, for lines, only vertices that are not
17115      *     endpoints will be returned.  If not provided, all vertices will
17116      *     be returned.
17117      *
17118      * Returns:
17119      * {Array} A list of all vertices in the geometry.
17120      */
17121     getVertices: function(nodes) {
17122         var vertices;
17123         if(nodes === true) {
17124             vertices = [
17125                 this.components[0],
17126                 this.components[this.components.length-1]
17127             ];
17128         } else if (nodes === false) {
17129             vertices = this.components.slice(1, this.components.length-1);
17130         } else {
17131             vertices = this.components.slice();
17132         }
17133         return vertices;
17134     },
17135
17136     /**
17137      * APIMethod: distanceTo
17138      * Calculate the closest distance between two geometries (on the x-y plane).
17139      *
17140      * Parameters:
17141      * geometry - {<OpenLayers.Geometry>} The target geometry.
17142      * options - {Object} Optional properties for configuring the distance
17143      *     calculation.
17144      *
17145      * Valid options:
17146      * details - {Boolean} Return details from the distance calculation.
17147      *     Default is false.
17148      * edge - {Boolean} Calculate the distance from this geometry to the
17149      *     nearest edge of the target geometry.  Default is true.  If true,
17150      *     calling distanceTo from a geometry that is wholly contained within
17151      *     the target will result in a non-zero distance.  If false, whenever
17152      *     geometries intersect, calling distanceTo will return 0.  If false,
17153      *     details cannot be returned.
17154      *
17155      * Returns:
17156      * {Number | Object} The distance between this geometry and the target.
17157      *     If details is true, the return will be an object with distance,
17158      *     x0, y0, x1, and x2 properties.  The x0 and y0 properties represent
17159      *     the coordinates of the closest point on this geometry. The x1 and y1
17160      *     properties represent the coordinates of the closest point on the
17161      *     target geometry.
17162      */
17163     distanceTo: function(geometry, options) {
17164         var edge = !(options && options.edge === false);
17165         var details = edge && options && options.details;
17166         var result, best = {};
17167         var min = Number.POSITIVE_INFINITY;
17168         if(geometry instanceof OpenLayers.Geometry.Point) {
17169             var segs = this.getSortedSegments();
17170             var x = geometry.x;
17171             var y = geometry.y;
17172             var seg;
17173             for(var i=0, len=segs.length; i<len; ++i) {
17174                 seg = segs[i];
17175                 result = OpenLayers.Geometry.distanceToSegment(geometry, seg);
17176                 if(result.distance < min) {
17177                     min = result.distance;
17178                     if(details) {
17179                         best = {
17180                             distance: min,
17181                             x0: result.x, y0: result.y,
17182                             x1: x, y1: y,
17183                             index: i,
17184                             indexDistance: new OpenLayers.Geometry.Point(seg.x1, seg.y1).distanceTo(geometry)
17185                         };
17186                     } else {
17187                         best = min;
17188                     }
17189                     if(min === 0) {
17190                         break;
17191                     }
17192                 }
17193             }
17194         } else if(geometry instanceof OpenLayers.Geometry.LineString) { 
17195             var segs0 = this.getSortedSegments();
17196             var segs1 = geometry.getSortedSegments();
17197             var seg0, seg1, intersection, x0, y0;
17198             var len1 = segs1.length;
17199             var interOptions = {point: true};
17200             outer: for(var i=0, len=segs0.length; i<len; ++i) {
17201                 seg0 = segs0[i];
17202                 x0 = seg0.x1;
17203                 y0 = seg0.y1;
17204                 for(var j=0; j<len1; ++j) {
17205                     seg1 = segs1[j];
17206                     intersection = OpenLayers.Geometry.segmentsIntersect(seg0, seg1, interOptions);
17207                     if(intersection) {
17208                         min = 0;
17209                         best = {
17210                             distance: 0,
17211                             x0: intersection.x, y0: intersection.y,
17212                             x1: intersection.x, y1: intersection.y
17213                         };
17214                         break outer;
17215                     } else {
17216                         result = OpenLayers.Geometry.distanceToSegment({x: x0, y: y0}, seg1);
17217                         if(result.distance < min) {
17218                             min = result.distance;
17219                             best = {
17220                                 distance: min,
17221                                 x0: x0, y0: y0,
17222                                 x1: result.x, y1: result.y
17223                             };
17224                         }
17225                     }
17226                 }
17227             }
17228             if(!details) {
17229                 best = best.distance;
17230             }
17231             if(min !== 0) {
17232                 // check the final vertex in this line's sorted segments
17233                 if(seg0) {
17234                     result = geometry.distanceTo(
17235                         new OpenLayers.Geometry.Point(seg0.x2, seg0.y2),
17236                         options
17237                     );
17238                     var dist = details ? result.distance : result;
17239                     if(dist < min) {
17240                         if(details) {
17241                             best = {
17242                                 distance: min,
17243                                 x0: result.x1, y0: result.y1,
17244                                 x1: result.x0, y1: result.y0
17245                             };
17246                         } else {
17247                             best = dist;
17248                         }
17249                     }
17250                 }
17251             }
17252         } else {
17253             best = geometry.distanceTo(this, options);
17254             // swap since target comes from this line
17255             if(details) {
17256                 best = {
17257                     distance: best.distance,
17258                     x0: best.x1, y0: best.y1,
17259                     x1: best.x0, y1: best.y0
17260                 };
17261             }
17262         }
17263         return best;
17264     },
17265     
17266     /**
17267      * APIMethod: simplify
17268      * This function will return a simplified LineString.
17269      * Simplification is based on the Douglas-Peucker algorithm.
17270      *
17271      *
17272      * Parameters:
17273      * tolerance - {number} threshold for simplification in map units
17274      *
17275      * Returns:
17276      * {OpenLayers.Geometry.LineString} the simplified LineString
17277      */
17278     simplify: function(tolerance){
17279         if (this && this !== null) {
17280             var points = this.getVertices();
17281             if (points.length < 3) {
17282                 return this;
17283             }
17284     
17285             var compareNumbers = function(a, b){
17286                 return (a-b);
17287             };
17288     
17289             /**
17290              * Private function doing the Douglas-Peucker reduction
17291              */
17292             var douglasPeuckerReduction = function(points, firstPoint, lastPoint, tolerance){
17293                 var maxDistance = 0;
17294                 var indexFarthest = 0;
17295     
17296                 for (var index = firstPoint, distance; index < lastPoint; index++) {
17297                     distance = perpendicularDistance(points[firstPoint], points[lastPoint], points[index]);
17298                     if (distance > maxDistance) {
17299                         maxDistance = distance;
17300                         indexFarthest = index;
17301                     }
17302                 }
17303     
17304                 if (maxDistance > tolerance && indexFarthest != firstPoint) {
17305                     //Add the largest point that exceeds the tolerance
17306                     pointIndexsToKeep.push(indexFarthest);
17307                     douglasPeuckerReduction(points, firstPoint, indexFarthest, tolerance);
17308                     douglasPeuckerReduction(points, indexFarthest, lastPoint, tolerance);
17309                 }
17310             };
17311     
17312             /**
17313              * Private function calculating the perpendicular distance
17314              * TODO: check whether OpenLayers.Geometry.LineString::distanceTo() is faster or slower
17315              */
17316             var perpendicularDistance = function(point1, point2, point){
17317                 //Area = |(1/2)(x1y2 + x2y3 + x3y1 - x2y1 - x3y2 - x1y3)|   *Area of triangle
17318                 //Base = v((x1-x2)²+(x1-x2)²)                               *Base of Triangle*
17319                 //Area = .5*Base*H                                          *Solve for height
17320                 //Height = Area/.5/Base
17321     
17322                 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));
17323                 var bottom = Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2));
17324                 var height = area / bottom * 2;
17325     
17326                 return height;
17327             };
17328     
17329             var firstPoint = 0;
17330             var lastPoint = points.length - 1;
17331             var pointIndexsToKeep = [];
17332     
17333             //Add the first and last index to the keepers
17334             pointIndexsToKeep.push(firstPoint);
17335             pointIndexsToKeep.push(lastPoint);
17336     
17337             //The first and the last point cannot be the same
17338             while (points[firstPoint].equals(points[lastPoint])) {
17339                 lastPoint--;
17340                 //Addition: the first point not equal to first point in the LineString is kept as well
17341                 pointIndexsToKeep.push(lastPoint);
17342             }
17343     
17344             douglasPeuckerReduction(points, firstPoint, lastPoint, tolerance);
17345             var returnPoints = [];
17346             pointIndexsToKeep.sort(compareNumbers);
17347             for (var index = 0; index < pointIndexsToKeep.length; index++) {
17348                 returnPoints.push(points[pointIndexsToKeep[index]]);
17349             }
17350             return new OpenLayers.Geometry.LineString(returnPoints);
17351     
17352         }
17353         else {
17354             return this;
17355         }
17356     },
17357
17358     CLASS_NAME: "OpenLayers.Geometry.LineString"
17359 });
17360
17361
17362 /**
17363  * Function: OpenLayers.Geometry.LineString.geodesic
17364  *
17365  * Parameters:
17366  * interpolate - {function(number): OpenLayers.Geometry.Point} Interpolate
17367  *     function.
17368  * transform - {function(OpenLayers.Geometry.Point): OpenLayers.Geometry.Point}
17369  *     Transform from longitude/latitude to projected coordinates.
17370  * squaredTolerance - {number} Squared tolerance.
17371  *
17372  * Returns:
17373  * {OpenLayers.Geometry.LineString}
17374  */
17375 OpenLayers.Geometry.LineString.geodesic =
17376         function(interpolate, transform, squaredTolerance) {
17377     // FIXME reduce garbage generation
17378     // FIXME optimize stack operations
17379
17380     var components = [];
17381
17382     var geoA = interpolate(0);
17383     var geoB = interpolate(1);
17384
17385     var a = transform(geoA);
17386     var b = transform(geoB);
17387
17388     var geoStack = [geoB, geoA];
17389     var stack = [b, a];
17390     var fractionStack = [1, 0];
17391
17392     var fractions = {};
17393
17394     var maxIterations = 1e5;
17395     var geoM, m, fracA, fracB, fracM, key;
17396
17397     while (--maxIterations > 0 && fractionStack.length > 0) {
17398         // Pop the a coordinate off the stack
17399         fracA = fractionStack.pop();
17400         geoA = geoStack.pop();
17401         a = stack.pop();
17402         // Add the a coordinate if it has not been added yet
17403         key = fracA.toString();
17404         if (!(key in fractions)) {
17405             components.push(a);
17406             fractions[key] = true;
17407         }
17408         // Pop the b coordinate off the stack
17409         fracB = fractionStack.pop();
17410         geoB = geoStack.pop();
17411         b = stack.pop();
17412         // Find the m point between the a and b coordinates
17413         fracM = (fracA + fracB) / 2;
17414         geoM = interpolate(fracM);
17415         m = transform(geoM);
17416         if (OpenLayers.Geometry.distanceSquaredToSegment(m, {x1: a.x, y1: a.y,
17417                 x2: b.x, y2: b.y}).distance < squaredTolerance) {
17418             // If the m point is sufficiently close to the straight line, then
17419             // we discard it. Just use the b coordinate and move on to the next
17420             // line segment.
17421             components.push(b);
17422             key = fracB.toString();
17423             fractions[key] = true;
17424         } else {
17425             // Otherwise, we need to subdivide the current line segment.
17426             // Split it into two and push the two line segments onto the stack.
17427             fractionStack.push(fracB, fracM, fracM, fracA);
17428             stack.push(b, m, m, a);
17429             geoStack.push(geoB, geoM, geoM, geoA);
17430         }
17431     }
17432
17433     return new OpenLayers.Geometry.LineString(components);
17434 };
17435
17436
17437 /**
17438  * Function: OpenLayers.Geometry.LineString.geodesicMeridian
17439  * Generate a meridian (line at constant longitude).
17440  *
17441  * Parameters:
17442  * lon - {number} Longitude.
17443  * lat1 - {number} Latitude 1.
17444  * lat2 - {number} Latitude 2.
17445  * projection - {OpenLayers.Projection} Projection.
17446  * squaredTolerance - {number} Squared tolerance.
17447  *
17448  * Returns:
17449  * {OpenLayers.Geometry.LineString} Line geometry for the meridian at <lon>.
17450  */
17451 OpenLayers.Geometry.LineString.geodesicMeridian =
17452         function(lon, lat1, lat2, projection, squaredTolerance) {
17453     var epsg4326Projection = new OpenLayers.Projection('EPSG:4326');
17454     return OpenLayers.Geometry.LineString.geodesic(
17455         function(frac) {
17456             return new OpenLayers.Geometry.Point(
17457                     lon, lat1 + ((lat2 - lat1) * frac));
17458         },
17459         function(point) {
17460             return point.transform(epsg4326Projection, projection);
17461         },
17462         squaredTolerance
17463   );
17464 };
17465
17466
17467 /**
17468  * Function: OpenLayers.Geometry.LineString.geodesicParallel
17469  * Generate a parallel (line at constant latitude).
17470  *
17471  * Parameters:
17472  * lat - {number} Latitude.
17473  * lon1 - {number} Longitude 1.
17474  * lon2 - {number} Longitude 2.
17475  * projection {OpenLayers.Projection} Projection.
17476  * squaredTolerance - {number} Squared tolerance.
17477  *
17478  * Returns:
17479  * {OpenLayers.Geometry.LineString} Line geometry for the parallel at <lat>.
17480  */
17481 OpenLayers.Geometry.LineString.geodesicParallel =
17482         function(lat, lon1, lon2, projection, squaredTolerance) {
17483     var epsg4326Projection = new OpenLayers.Projection('EPSG:4326');
17484     return OpenLayers.Geometry.LineString.geodesic(
17485         function(frac) {
17486             return new OpenLayers.Geometry.Point(
17487                     lon1 + ((lon2 - lon1) * frac), lat);
17488         },
17489         function(point) {
17490             return point.transform(epsg4326Projection, projection);
17491         },
17492         squaredTolerance
17493     );
17494 };
17495
17496 /* ======================================================================
17497     OpenLayers/Geometry/MultiLineString.js
17498    ====================================================================== */
17499
17500 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
17501  * full list of contributors). Published under the 2-clause BSD license.
17502  * See license.txt in the OpenLayers distribution or repository for the
17503  * full text of the license. */
17504
17505 /**
17506  * @requires OpenLayers/Geometry/Collection.js
17507  * @requires OpenLayers/Geometry/LineString.js
17508  */
17509
17510 /**
17511  * Class: OpenLayers.Geometry.MultiLineString
17512  * A MultiLineString is a geometry with multiple <OpenLayers.Geometry.LineString>
17513  * components.
17514  * 
17515  * Inherits from:
17516  *  - <OpenLayers.Geometry.Collection>
17517  *  - <OpenLayers.Geometry> 
17518  */
17519 OpenLayers.Geometry.MultiLineString = OpenLayers.Class(
17520   OpenLayers.Geometry.Collection, {
17521
17522     /**
17523      * Property: componentTypes
17524      * {Array(String)} An array of class names representing the types of
17525      * components that the collection can include.  A null value means the
17526      * component types are not restricted.
17527      */
17528     componentTypes: ["OpenLayers.Geometry.LineString"],
17529
17530     /**
17531      * Constructor: OpenLayers.Geometry.MultiLineString
17532      * Constructor for a MultiLineString Geometry.
17533      *
17534      * Parameters: 
17535      * components - {Array(<OpenLayers.Geometry.LineString>)} 
17536      *
17537      */
17538     
17539     /**
17540      * Method: split
17541      * Use this geometry (the source) to attempt to split a target geometry.
17542      * 
17543      * Parameters:
17544      * geometry - {<OpenLayers.Geometry>} The target geometry.
17545      * options - {Object} Properties of this object will be used to determine
17546      *     how the split is conducted.
17547      *
17548      * Valid options:
17549      * mutual - {Boolean} Split the source geometry in addition to the target
17550      *     geometry.  Default is false.
17551      * edge - {Boolean} Allow splitting when only edges intersect.  Default is
17552      *     true.  If false, a vertex on the source must be within the tolerance
17553      *     distance of the intersection to be considered a split.
17554      * tolerance - {Number} If a non-null value is provided, intersections
17555      *     within the tolerance distance of an existing vertex on the source
17556      *     will be assumed to occur at the vertex.
17557      * 
17558      * Returns:
17559      * {Array} A list of geometries (of this same type as the target) that
17560      *     result from splitting the target with the source geometry.  The
17561      *     source and target geometry will remain unmodified.  If no split
17562      *     results, null will be returned.  If mutual is true and a split
17563      *     results, return will be an array of two arrays - the first will be
17564      *     all geometries that result from splitting the source geometry and
17565      *     the second will be all geometries that result from splitting the
17566      *     target geometry.
17567      */
17568     split: function(geometry, options) {
17569         var results = null;
17570         var mutual = options && options.mutual;
17571         var splits, sourceLine, sourceLines, sourceSplit, targetSplit;
17572         var sourceParts = [];
17573         var targetParts = [geometry];
17574         for(var i=0, len=this.components.length; i<len; ++i) {
17575             sourceLine = this.components[i];
17576             sourceSplit = false;
17577             for(var j=0; j < targetParts.length; ++j) { 
17578                 splits = sourceLine.split(targetParts[j], options);
17579                 if(splits) {
17580                     if(mutual) {
17581                         sourceLines = splits[0];
17582                         for(var k=0, klen=sourceLines.length; k<klen; ++k) {
17583                             if(k===0 && sourceParts.length) {
17584                                 sourceParts[sourceParts.length-1].addComponent(
17585                                     sourceLines[k]
17586                                 );
17587                             } else {
17588                                 sourceParts.push(
17589                                     new OpenLayers.Geometry.MultiLineString([
17590                                         sourceLines[k]
17591                                     ])
17592                                 );
17593                             }
17594                         }
17595                         sourceSplit = true;
17596                         splits = splits[1];
17597                     }
17598                     if(splits.length) {
17599                         // splice in new target parts
17600                         splits.unshift(j, 1);
17601                         Array.prototype.splice.apply(targetParts, splits);
17602                         break;
17603                     }
17604                 }
17605             }
17606             if(!sourceSplit) {
17607                 // source line was not hit
17608                 if(sourceParts.length) {
17609                     // add line to existing multi
17610                     sourceParts[sourceParts.length-1].addComponent(
17611                         sourceLine.clone()
17612                     );
17613                 } else {
17614                     // create a fresh multi
17615                     sourceParts = [
17616                         new OpenLayers.Geometry.MultiLineString(
17617                             sourceLine.clone()
17618                         )
17619                     ];
17620                 }
17621             }
17622         }
17623         if(sourceParts && sourceParts.length > 1) {
17624             sourceSplit = true;
17625         } else {
17626             sourceParts = [];
17627         }
17628         if(targetParts && targetParts.length > 1) {
17629             targetSplit = true;
17630         } else {
17631             targetParts = [];
17632         }
17633         if(sourceSplit || targetSplit) {
17634             if(mutual) {
17635                 results = [sourceParts, targetParts];
17636             } else {
17637                 results = targetParts;
17638             }
17639         }
17640         return results;
17641     },
17642     
17643     /**
17644      * Method: splitWith
17645      * Split this geometry (the target) with the given geometry (the source).
17646      *
17647      * Parameters:
17648      * geometry - {<OpenLayers.Geometry>} A geometry used to split this
17649      *     geometry (the source).
17650      * options - {Object} Properties of this object will be used to determine
17651      *     how the split is conducted.
17652      *
17653      * Valid options:
17654      * mutual - {Boolean} Split the source geometry in addition to the target
17655      *     geometry.  Default is false.
17656      * edge - {Boolean} Allow splitting when only edges intersect.  Default is
17657      *     true.  If false, a vertex on the source must be within the tolerance
17658      *     distance of the intersection to be considered a split.
17659      * tolerance - {Number} If a non-null value is provided, intersections
17660      *     within the tolerance distance of an existing vertex on the source
17661      *     will be assumed to occur at the vertex.
17662      * 
17663      * Returns:
17664      * {Array} A list of geometries (of this same type as the target) that
17665      *     result from splitting the target with the source geometry.  The
17666      *     source and target geometry will remain unmodified.  If no split
17667      *     results, null will be returned.  If mutual is true and a split
17668      *     results, return will be an array of two arrays - the first will be
17669      *     all geometries that result from splitting the source geometry and
17670      *     the second will be all geometries that result from splitting the
17671      *     target geometry.
17672      */
17673     splitWith: function(geometry, options) {
17674         var results = null;
17675         var mutual = options && options.mutual;
17676         var splits, targetLine, sourceLines, sourceSplit, targetSplit, sourceParts, targetParts;
17677         if(geometry instanceof OpenLayers.Geometry.LineString) {
17678             targetParts = [];
17679             sourceParts = [geometry];
17680             for(var i=0, len=this.components.length; i<len; ++i) {
17681                 targetSplit = false;
17682                 targetLine = this.components[i];
17683                 for(var j=0; j<sourceParts.length; ++j) {
17684                     splits = sourceParts[j].split(targetLine, options);
17685                     if(splits) {
17686                         if(mutual) {
17687                             sourceLines = splits[0];
17688                             if(sourceLines.length) {
17689                                 // splice in new source parts
17690                                 sourceLines.unshift(j, 1);
17691                                 Array.prototype.splice.apply(sourceParts, sourceLines);
17692                                 j += sourceLines.length - 2;
17693                             }
17694                             splits = splits[1];
17695                             if(splits.length === 0) {
17696                                 splits = [targetLine.clone()];
17697                             }
17698                         }
17699                         for(var k=0, klen=splits.length; k<klen; ++k) {
17700                             if(k===0 && targetParts.length) {
17701                                 targetParts[targetParts.length-1].addComponent(
17702                                     splits[k]
17703                                 );
17704                             } else {
17705                                 targetParts.push(
17706                                     new OpenLayers.Geometry.MultiLineString([
17707                                         splits[k]
17708                                     ])
17709                                 );
17710                             }
17711                         }
17712                         targetSplit = true;                    
17713                     }
17714                 }
17715                 if(!targetSplit) {
17716                     // target component was not hit
17717                     if(targetParts.length) {
17718                         // add it to any existing multi-line
17719                         targetParts[targetParts.length-1].addComponent(
17720                             targetLine.clone()
17721                         );
17722                     } else {
17723                         // or start with a fresh multi-line
17724                         targetParts = [
17725                             new OpenLayers.Geometry.MultiLineString([
17726                                 targetLine.clone()
17727                             ])
17728                         ];
17729                     }
17730                     
17731                 }
17732             }
17733         } else {
17734             results = geometry.split(this);
17735         }
17736         if(sourceParts && sourceParts.length > 1) {
17737             sourceSplit = true;
17738         } else {
17739             sourceParts = [];
17740         }
17741         if(targetParts && targetParts.length > 1) {
17742             targetSplit = true;
17743         } else {
17744             targetParts = [];
17745         }
17746         if(sourceSplit || targetSplit) {
17747             if(mutual) {
17748                 results = [sourceParts, targetParts];
17749             } else {
17750                 results = targetParts;
17751             }
17752         }
17753         return results;
17754     },
17755
17756     CLASS_NAME: "OpenLayers.Geometry.MultiLineString"
17757 });
17758 /* ======================================================================
17759     OpenLayers/Geometry/LinearRing.js
17760    ====================================================================== */
17761
17762 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
17763  * full list of contributors). Published under the 2-clause BSD license.
17764  * See license.txt in the OpenLayers distribution or repository for the
17765  * full text of the license. */
17766
17767 /**
17768  * @requires OpenLayers/Geometry/LineString.js
17769  */
17770
17771 /**
17772  * Class: OpenLayers.Geometry.LinearRing
17773  * 
17774  * A Linear Ring is a special LineString which is closed. It closes itself 
17775  * automatically on every addPoint/removePoint by adding a copy of the first
17776  * point as the last point. 
17777  * 
17778  * Also, as it is the first in the line family to close itself, a getArea()
17779  * function is defined to calculate the enclosed area of the linearRing
17780  * 
17781  * Inherits:
17782  *  - <OpenLayers.Geometry.LineString>
17783  */
17784 OpenLayers.Geometry.LinearRing = OpenLayers.Class(
17785   OpenLayers.Geometry.LineString, {
17786
17787     /**
17788      * Property: componentTypes
17789      * {Array(String)} An array of class names representing the types of 
17790      *                 components that the collection can include.  A null 
17791      *                 value means the component types are not restricted.
17792      */
17793     componentTypes: ["OpenLayers.Geometry.Point"],
17794
17795     /**
17796      * Constructor: OpenLayers.Geometry.LinearRing
17797      * Linear rings are constructed with an array of points.  This array
17798      *     can represent a closed or open ring.  If the ring is open (the last
17799      *     point does not equal the first point), the constructor will close
17800      *     the ring.  If the ring is already closed (the last point does equal
17801      *     the first point), it will be left closed.
17802      * 
17803      * Parameters:
17804      * points - {Array(<OpenLayers.Geometry.Point>)} points
17805      */
17806
17807     /**
17808      * APIMethod: addComponent
17809      * Adds a point to geometry components.  If the point is to be added to
17810      *     the end of the components array and it is the same as the last point
17811      *     already in that array, the duplicate point is not added.  This has 
17812      *     the effect of closing the ring if it is not already closed, and 
17813      *     doing the right thing if it is already closed.  This behavior can 
17814      *     be overridden by calling the method with a non-null index as the 
17815      *     second argument.
17816      *
17817      * Parameters:
17818      * point - {<OpenLayers.Geometry.Point>}
17819      * index - {Integer} Index into the array to insert the component
17820      * 
17821      * Returns:
17822      * {Boolean} Was the Point successfully added?
17823      */
17824     addComponent: function(point, index) {
17825         var added = false;
17826
17827         //remove last point
17828         var lastPoint = this.components.pop();
17829
17830         // given an index, add the point
17831         // without an index only add non-duplicate points
17832         if(index != null || !point.equals(lastPoint)) {
17833             added = OpenLayers.Geometry.Collection.prototype.addComponent.apply(this, 
17834                                                                     arguments);
17835         }
17836
17837         //append copy of first point
17838         var firstPoint = this.components[0];
17839         OpenLayers.Geometry.Collection.prototype.addComponent.apply(this, 
17840                                                                 [firstPoint]);
17841         
17842         return added;
17843     },
17844     
17845     /**
17846      * APIMethod: removeComponent
17847      * Removes a point from geometry components.
17848      *
17849      * Parameters:
17850      * point - {<OpenLayers.Geometry.Point>}
17851      *
17852      * Returns: 
17853      * {Boolean} The component was removed.
17854      */
17855     removeComponent: function(point) {
17856         var removed = this.components && (this.components.length > 3);
17857         if (removed) {
17858             //remove last point
17859             this.components.pop();
17860             
17861             //remove our point
17862             OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this, 
17863                                                                     arguments);
17864             //append copy of first point
17865             var firstPoint = this.components[0];
17866             OpenLayers.Geometry.Collection.prototype.addComponent.apply(this, 
17867                                                                 [firstPoint]);
17868         }
17869         return removed;
17870     },
17871     
17872     /**
17873      * APIMethod: move
17874      * Moves a geometry by the given displacement along positive x and y axes.
17875      *     This modifies the position of the geometry and clears the cached
17876      *     bounds.
17877      *
17878      * Parameters:
17879      * x - {Float} Distance to move geometry in positive x direction. 
17880      * y - {Float} Distance to move geometry in positive y direction.
17881      */
17882     move: function(x, y) {
17883         for(var i = 0, len=this.components.length; i<len - 1; i++) {
17884             this.components[i].move(x, y);
17885         }
17886     },
17887
17888     /**
17889      * APIMethod: rotate
17890      * Rotate a geometry around some origin
17891      *
17892      * Parameters:
17893      * angle - {Float} Rotation angle in degrees (measured counterclockwise
17894      *                 from the positive x-axis)
17895      * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
17896      */
17897     rotate: function(angle, origin) {
17898         for(var i=0, len=this.components.length; i<len - 1; ++i) {
17899             this.components[i].rotate(angle, origin);
17900         }
17901     },
17902
17903     /**
17904      * APIMethod: resize
17905      * Resize a geometry relative to some origin.  Use this method to apply
17906      *     a uniform scaling to a geometry.
17907      *
17908      * Parameters:
17909      * scale - {Float} Factor by which to scale the geometry.  A scale of 2
17910      *                 doubles the size of the geometry in each dimension
17911      *                 (lines, for example, will be twice as long, and polygons
17912      *                 will have four times the area).
17913      * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
17914      * ratio - {Float} Optional x:y ratio for resizing.  Default ratio is 1.
17915      * 
17916      * Returns:
17917      * {<OpenLayers.Geometry>} - The current geometry. 
17918      */
17919     resize: function(scale, origin, ratio) {
17920         for(var i=0, len=this.components.length; i<len - 1; ++i) {
17921             this.components[i].resize(scale, origin, ratio);
17922         }
17923         return this;
17924     },
17925     
17926     /**
17927      * APIMethod: transform
17928      * Reproject the components geometry from source to dest.
17929      *
17930      * Parameters:
17931      * source - {<OpenLayers.Projection>}
17932      * dest - {<OpenLayers.Projection>}
17933      * 
17934      * Returns:
17935      * {<OpenLayers.Geometry>} 
17936      */
17937     transform: function(source, dest) {
17938         if (source && dest) {
17939             for (var i=0, len=this.components.length; i<len - 1; i++) {
17940                 var component = this.components[i];
17941                 component.transform(source, dest);
17942             }
17943             this.bounds = null;
17944         }
17945         return this;
17946     },
17947     
17948     /**
17949      * APIMethod: getCentroid
17950      *
17951      * Returns:
17952      * {<OpenLayers.Geometry.Point>} The centroid of the collection
17953      */
17954     getCentroid: function() {
17955         if (this.components) {
17956             var len = this.components.length;
17957             if (len > 0 && len <= 2) {
17958                 return this.components[0].clone();
17959             } else if (len > 2) {
17960                 var sumX = 0.0;
17961                 var sumY = 0.0;
17962                 var x0 = this.components[0].x;
17963                 var y0 = this.components[0].y;
17964                 var area = -1 * this.getArea();
17965                 if (area != 0) {
17966                     for (var i = 0; i < len - 1; i++) {
17967                         var b = this.components[i];
17968                         var c = this.components[i+1];
17969                         sumX += (b.x + c.x - 2 * x0) * ((b.x - x0) * (c.y - y0) - (c.x - x0) * (b.y - y0));
17970                         sumY += (b.y + c.y - 2 * y0) * ((b.x - x0) * (c.y - y0) - (c.x - x0) * (b.y - y0));
17971                     }
17972                     var x = x0 + sumX / (6 * area);
17973                     var y = y0 + sumY / (6 * area);
17974                 } else {
17975                     for (var i = 0; i < len - 1; i++) {
17976                         sumX += this.components[i].x;
17977                         sumY += this.components[i].y;
17978                     }
17979                     var x = sumX / (len - 1);
17980                     var y = sumY / (len - 1);
17981                 }
17982                 return new OpenLayers.Geometry.Point(x, y);
17983             } else {
17984                 return null;
17985             }
17986         }
17987     },
17988
17989     /**
17990      * APIMethod: getArea
17991      * Note - The area is positive if the ring is oriented CW, otherwise
17992      *         it will be negative.
17993      * 
17994      * Returns:
17995      * {Float} The signed area for a ring.
17996      */
17997     getArea: function() {
17998         var area = 0.0;
17999         if ( this.components && (this.components.length > 2)) {
18000             var sum = 0.0;
18001             for (var i=0, len=this.components.length; i<len - 1; i++) {
18002                 var b = this.components[i];
18003                 var c = this.components[i+1];
18004                 sum += (b.x + c.x) * (c.y - b.y);
18005             }
18006             area = - sum / 2.0;
18007         }
18008         return area;
18009     },
18010     
18011     /**
18012      * APIMethod: getGeodesicArea
18013      * Calculate the approximate area of the polygon were it projected onto
18014      *     the earth.  Note that this area will be positive if ring is oriented
18015      *     clockwise, otherwise it will be negative.
18016      *
18017      * Parameters:
18018      * projection - {<OpenLayers.Projection>} The spatial reference system
18019      *     for the geometry coordinates.  If not provided, Geographic/WGS84 is
18020      *     assumed.
18021      * 
18022      * Reference:
18023      * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
18024      *     Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
18025      *     Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
18026      *
18027      * Returns:
18028      * {float} The approximate signed geodesic area of the polygon in square
18029      *     meters.
18030      */
18031     getGeodesicArea: function(projection) {
18032         var ring = this;  // so we can work with a clone if needed
18033         if(projection) {
18034             var gg = new OpenLayers.Projection("EPSG:4326");
18035             if(!gg.equals(projection)) {
18036                 ring = this.clone().transform(projection, gg);
18037             }
18038         }
18039         var area = 0.0;
18040         var len = ring.components && ring.components.length;
18041         if(len > 2) {
18042             var p1, p2;
18043             for(var i=0; i<len-1; i++) {
18044                 p1 = ring.components[i];
18045                 p2 = ring.components[i+1];
18046                 area += OpenLayers.Util.rad(p2.x - p1.x) *
18047                         (2 + Math.sin(OpenLayers.Util.rad(p1.y)) +
18048                         Math.sin(OpenLayers.Util.rad(p2.y)));
18049             }
18050             area = area * OpenLayers.Util.VincentyConstants.a * OpenLayers.Util.VincentyConstants.a / 2.0;
18051         }
18052         return area;
18053     },
18054     
18055     /**
18056      * Method: containsPoint
18057      * Test if a point is inside a linear ring.  For the case where a point
18058      *     is coincident with a linear ring edge, returns 1.  Otherwise,
18059      *     returns boolean.
18060      *
18061      * Parameters:
18062      * point - {<OpenLayers.Geometry.Point>}
18063      *
18064      * Returns:
18065      * {Boolean | Number} The point is inside the linear ring.  Returns 1 if
18066      *     the point is coincident with an edge.  Returns boolean otherwise.
18067      */
18068     containsPoint: function(point) {
18069         var approx = OpenLayers.Number.limitSigDigs;
18070         var digs = 14;
18071         var px = approx(point.x, digs);
18072         var py = approx(point.y, digs);
18073         function getX(y, x1, y1, x2, y2) {
18074             return (y - y2) * ((x2 - x1) / (y2 - y1)) + x2;
18075         }
18076         var numSeg = this.components.length - 1;
18077         var start, end, x1, y1, x2, y2, cx, cy;
18078         var crosses = 0;
18079         for(var i=0; i<numSeg; ++i) {
18080             start = this.components[i];
18081             x1 = approx(start.x, digs);
18082             y1 = approx(start.y, digs);
18083             end = this.components[i + 1];
18084             x2 = approx(end.x, digs);
18085             y2 = approx(end.y, digs);
18086             
18087             /**
18088              * The following conditions enforce five edge-crossing rules:
18089              *    1. points coincident with edges are considered contained;
18090              *    2. an upward edge includes its starting endpoint, and
18091              *    excludes its final endpoint;
18092              *    3. a downward edge excludes its starting endpoint, and
18093              *    includes its final endpoint;
18094              *    4. horizontal edges are excluded; and
18095              *    5. the edge-ray intersection point must be strictly right
18096              *    of the point P.
18097              */
18098             if(y1 == y2) {
18099                 // horizontal edge
18100                 if(py == y1) {
18101                     // point on horizontal line
18102                     if(x1 <= x2 && (px >= x1 && px <= x2) || // right or vert
18103                        x1 >= x2 && (px <= x1 && px >= x2)) { // left or vert
18104                         // point on edge
18105                         crosses = -1;
18106                         break;
18107                     }
18108                 }
18109                 // ignore other horizontal edges
18110                 continue;
18111             }
18112             cx = approx(getX(py, x1, y1, x2, y2), digs);
18113             if(cx == px) {
18114                 // point on line
18115                 if(y1 < y2 && (py >= y1 && py <= y2) || // upward
18116                    y1 > y2 && (py <= y1 && py >= y2)) { // downward
18117                     // point on edge
18118                     crosses = -1;
18119                     break;
18120                 }
18121             }
18122             if(cx <= px) {
18123                 // no crossing to the right
18124                 continue;
18125             }
18126             if(x1 != x2 && (cx < Math.min(x1, x2) || cx > Math.max(x1, x2))) {
18127                 // no crossing
18128                 continue;
18129             }
18130             if(y1 < y2 && (py >= y1 && py < y2) || // upward
18131                y1 > y2 && (py < y1 && py >= y2)) { // downward
18132                 ++crosses;
18133             }
18134         }
18135         var contained = (crosses == -1) ?
18136             // on edge
18137             1 :
18138             // even (out) or odd (in)
18139             !!(crosses & 1);
18140
18141         return contained;
18142     },
18143
18144     /**
18145      * APIMethod: intersects
18146      * Determine if the input geometry intersects this one.
18147      *
18148      * Parameters:
18149      * geometry - {<OpenLayers.Geometry>} Any type of geometry.
18150      *
18151      * Returns:
18152      * {Boolean} The input geometry intersects this one.
18153      */
18154     intersects: function(geometry) {
18155         var intersect = false;
18156         if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
18157             intersect = this.containsPoint(geometry);
18158         } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LineString") {
18159             intersect = geometry.intersects(this);
18160         } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
18161             intersect = OpenLayers.Geometry.LineString.prototype.intersects.apply(
18162                 this, [geometry]
18163             );
18164         } else {
18165             // check for component intersections
18166             for(var i=0, len=geometry.components.length; i<len; ++ i) {
18167                 intersect = geometry.components[i].intersects(this);
18168                 if(intersect) {
18169                     break;
18170                 }
18171             }
18172         }
18173         return intersect;
18174     },
18175
18176     /**
18177      * APIMethod: getVertices
18178      * Return a list of all points in this geometry.
18179      *
18180      * Parameters:
18181      * nodes - {Boolean} For lines, only return vertices that are
18182      *     endpoints.  If false, for lines, only vertices that are not
18183      *     endpoints will be returned.  If not provided, all vertices will
18184      *     be returned.
18185      *
18186      * Returns:
18187      * {Array} A list of all vertices in the geometry.
18188      */
18189     getVertices: function(nodes) {
18190         return (nodes === true) ? [] : this.components.slice(0, this.components.length-1);
18191     },
18192
18193     CLASS_NAME: "OpenLayers.Geometry.LinearRing"
18194 });
18195 /* ======================================================================
18196     OpenLayers/Geometry/Polygon.js
18197    ====================================================================== */
18198
18199 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
18200  * full list of contributors). Published under the 2-clause BSD license.
18201  * See license.txt in the OpenLayers distribution or repository for the
18202  * full text of the license. */
18203
18204 /**
18205  * @requires OpenLayers/Geometry/Collection.js
18206  * @requires OpenLayers/Geometry/LinearRing.js
18207  */
18208
18209 /**
18210  * Class: OpenLayers.Geometry.Polygon 
18211  * Polygon is a collection of Geometry.LinearRings. 
18212  * 
18213  * Inherits from:
18214  *  - <OpenLayers.Geometry.Collection> 
18215  *  - <OpenLayers.Geometry> 
18216  */
18217 OpenLayers.Geometry.Polygon = OpenLayers.Class(
18218   OpenLayers.Geometry.Collection, {
18219
18220     /**
18221      * Property: componentTypes
18222      * {Array(String)} An array of class names representing the types of
18223      * components that the collection can include.  A null value means the
18224      * component types are not restricted.
18225      */
18226     componentTypes: ["OpenLayers.Geometry.LinearRing"],
18227
18228     /**
18229      * Constructor: OpenLayers.Geometry.Polygon
18230      * Constructor for a Polygon geometry. 
18231      * The first ring (this.component[0])is the outer bounds of the polygon and 
18232      * all subsequent rings (this.component[1-n]) are internal holes.
18233      *
18234      *
18235      * Parameters:
18236      * components - {Array(<OpenLayers.Geometry.LinearRing>)} 
18237      */
18238
18239     /** 
18240      * APIMethod: getArea
18241      * Calculated by subtracting the areas of the internal holes from the 
18242      *   area of the outer hole.
18243      * 
18244      * Returns:
18245      * {float} The area of the geometry
18246      */
18247     getArea: function() {
18248         var area = 0.0;
18249         if ( this.components && (this.components.length > 0)) {
18250             area += Math.abs(this.components[0].getArea());
18251             for (var i=1, len=this.components.length; i<len; i++) {
18252                 area -= Math.abs(this.components[i].getArea());
18253             }
18254         }
18255         return area;
18256     },
18257
18258     /** 
18259      * APIMethod: getGeodesicArea
18260      * Calculate the approximate area of the polygon were it projected onto
18261      *     the earth.
18262      *
18263      * Parameters:
18264      * projection - {<OpenLayers.Projection>} The spatial reference system
18265      *     for the geometry coordinates.  If not provided, Geographic/WGS84 is
18266      *     assumed.
18267      * 
18268      * Reference:
18269      * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
18270      *     Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
18271      *     Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
18272      *
18273      * Returns:
18274      * {float} The approximate geodesic area of the polygon in square meters.
18275      */
18276     getGeodesicArea: function(projection) {
18277         var area = 0.0;
18278         if(this.components && (this.components.length > 0)) {
18279             area += Math.abs(this.components[0].getGeodesicArea(projection));
18280             for(var i=1, len=this.components.length; i<len; i++) {
18281                 area -= Math.abs(this.components[i].getGeodesicArea(projection));
18282             }
18283         }
18284         return area;
18285     },
18286
18287     /**
18288      * Method: containsPoint
18289      * Test if a point is inside a polygon.  Points on a polygon edge are
18290      *     considered inside.
18291      *
18292      * Parameters:
18293      * point - {<OpenLayers.Geometry.Point>}
18294      *
18295      * Returns:
18296      * {Boolean | Number} The point is inside the polygon.  Returns 1 if the
18297      *     point is on an edge.  Returns boolean otherwise.
18298      */
18299     containsPoint: function(point) {
18300         var numRings = this.components.length;
18301         var contained = false;
18302         if(numRings > 0) {
18303             // check exterior ring - 1 means on edge, boolean otherwise
18304             contained = this.components[0].containsPoint(point);
18305             if(contained !== 1) {
18306                 if(contained && numRings > 1) {
18307                     // check interior rings
18308                     var hole;
18309                     for(var i=1; i<numRings; ++i) {
18310                         hole = this.components[i].containsPoint(point);
18311                         if(hole) {
18312                             if(hole === 1) {
18313                                 // on edge
18314                                 contained = 1;
18315                             } else {
18316                                 // in hole
18317                                 contained = false;
18318                             }                            
18319                             break;
18320                         }
18321                     }
18322                 }
18323             }
18324         }
18325         return contained;
18326     },
18327
18328     /**
18329      * APIMethod: intersects
18330      * Determine if the input geometry intersects this one.
18331      *
18332      * Parameters:
18333      * geometry - {<OpenLayers.Geometry>} Any type of geometry.
18334      *
18335      * Returns:
18336      * {Boolean} The input geometry intersects this one.
18337      */
18338     intersects: function(geometry) {
18339         var intersect = false;
18340         var i, len;
18341         if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
18342             intersect = this.containsPoint(geometry);
18343         } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LineString" ||
18344                   geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
18345             // check if rings/linestrings intersect
18346             for(i=0, len=this.components.length; i<len; ++i) {
18347                 intersect = geometry.intersects(this.components[i]);
18348                 if(intersect) {
18349                     break;
18350                 }
18351             }
18352             if(!intersect) {
18353                 // check if this poly contains points of the ring/linestring
18354                 for(i=0, len=geometry.components.length; i<len; ++i) {
18355                     intersect = this.containsPoint(geometry.components[i]);
18356                     if(intersect) {
18357                         break;
18358                     }
18359                 }
18360             }
18361         } else {
18362             for(i=0, len=geometry.components.length; i<len; ++ i) {
18363                 intersect = this.intersects(geometry.components[i]);
18364                 if(intersect) {
18365                     break;
18366                 }
18367             }
18368         }
18369         // check case where this poly is wholly contained by another
18370         if(!intersect && geometry.CLASS_NAME == "OpenLayers.Geometry.Polygon") {
18371             // exterior ring points will be contained in the other geometry
18372             var ring = this.components[0];
18373             for(i=0, len=ring.components.length; i<len; ++i) {
18374                 intersect = geometry.containsPoint(ring.components[i]);
18375                 if(intersect) {
18376                     break;
18377                 }
18378             }
18379         }
18380         return intersect;
18381     },
18382
18383     /**
18384      * APIMethod: distanceTo
18385      * Calculate the closest distance between two geometries (on the x-y plane).
18386      *
18387      * Parameters:
18388      * geometry - {<OpenLayers.Geometry>} The target geometry.
18389      * options - {Object} Optional properties for configuring the distance
18390      *     calculation.
18391      *
18392      * Valid options:
18393      * details - {Boolean} Return details from the distance calculation.
18394      *     Default is false.
18395      * edge - {Boolean} Calculate the distance from this geometry to the
18396      *     nearest edge of the target geometry.  Default is true.  If true,
18397      *     calling distanceTo from a geometry that is wholly contained within
18398      *     the target will result in a non-zero distance.  If false, whenever
18399      *     geometries intersect, calling distanceTo will return 0.  If false,
18400      *     details cannot be returned.
18401      *
18402      * Returns:
18403      * {Number | Object} The distance between this geometry and the target.
18404      *     If details is true, the return will be an object with distance,
18405      *     x0, y0, x1, and y1 properties.  The x0 and y0 properties represent
18406      *     the coordinates of the closest point on this geometry. The x1 and y1
18407      *     properties represent the coordinates of the closest point on the
18408      *     target geometry.
18409      */
18410     distanceTo: function(geometry, options) {
18411         var edge = !(options && options.edge === false);
18412         var result;
18413         // this is the case where we might not be looking for distance to edge
18414         if(!edge && this.intersects(geometry)) {
18415             result = 0;
18416         } else {
18417             result = OpenLayers.Geometry.Collection.prototype.distanceTo.apply(
18418                 this, [geometry, options]
18419             );
18420         }
18421         return result;
18422     },
18423
18424     CLASS_NAME: "OpenLayers.Geometry.Polygon"
18425 });
18426
18427 /**
18428  * APIMethod: createRegularPolygon
18429  * Create a regular polygon around a radius. Useful for creating circles 
18430  * and the like.
18431  *
18432  * Parameters:
18433  * origin - {<OpenLayers.Geometry.Point>} center of polygon.
18434  * radius - {Float} distance to vertex, in map units.
18435  * sides - {Integer} Number of sides. 20 approximates a circle.
18436  * rotation - {Float} original angle of rotation, in degrees.
18437  */
18438 OpenLayers.Geometry.Polygon.createRegularPolygon = function(origin, radius, sides, rotation) {  
18439     var angle = Math.PI * ((1/sides) - (1/2));
18440     if(rotation) {
18441         angle += (rotation / 180) * Math.PI;
18442     }
18443     var rotatedAngle, x, y;
18444     var points = [];
18445     for(var i=0; i<sides; ++i) {
18446         rotatedAngle = angle + (i * 2 * Math.PI / sides);
18447         x = origin.x + (radius * Math.cos(rotatedAngle));
18448         y = origin.y + (radius * Math.sin(rotatedAngle));
18449         points.push(new OpenLayers.Geometry.Point(x, y));
18450     }
18451     var ring = new OpenLayers.Geometry.LinearRing(points);
18452     return new OpenLayers.Geometry.Polygon([ring]);
18453 };
18454 /* ======================================================================
18455     OpenLayers/Geometry/MultiPolygon.js
18456    ====================================================================== */
18457
18458 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
18459  * full list of contributors). Published under the 2-clause BSD license.
18460  * See license.txt in the OpenLayers distribution or repository for the
18461  * full text of the license. */
18462
18463 /**
18464  * @requires OpenLayers/Geometry/Collection.js
18465  * @requires OpenLayers/Geometry/Polygon.js
18466  */
18467
18468 /**
18469  * Class: OpenLayers.Geometry.MultiPolygon
18470  * MultiPolygon is a geometry with multiple <OpenLayers.Geometry.Polygon>
18471  * components.  Create a new instance with the <OpenLayers.Geometry.MultiPolygon>
18472  * constructor.
18473  * 
18474  * Inherits from:
18475  *  - <OpenLayers.Geometry.Collection>
18476  */
18477 OpenLayers.Geometry.MultiPolygon = OpenLayers.Class(
18478   OpenLayers.Geometry.Collection, {
18479
18480     /**
18481      * Property: componentTypes
18482      * {Array(String)} An array of class names representing the types of
18483      * components that the collection can include.  A null value means the
18484      * component types are not restricted.
18485      */
18486     componentTypes: ["OpenLayers.Geometry.Polygon"],
18487
18488     /**
18489      * Constructor: OpenLayers.Geometry.MultiPolygon
18490      * Create a new MultiPolygon geometry
18491      *
18492      * Parameters:
18493      * components - {Array(<OpenLayers.Geometry.Polygon>)} An array of polygons
18494      *              used to generate the MultiPolygon
18495      *
18496      */
18497
18498     CLASS_NAME: "OpenLayers.Geometry.MultiPolygon"
18499 });
18500 /* ======================================================================
18501     OpenLayers/Format/GML.js
18502    ====================================================================== */
18503
18504 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
18505  * full list of contributors). Published under the 2-clause BSD license.
18506  * See license.txt in the OpenLayers distribution or repository for the
18507  * full text of the license. */
18508
18509 /**
18510  * @requires OpenLayers/Format/XML.js
18511  * @requires OpenLayers/Feature/Vector.js
18512  * @requires OpenLayers/Geometry/Point.js
18513  * @requires OpenLayers/Geometry/MultiPoint.js
18514  * @requires OpenLayers/Geometry/LineString.js
18515  * @requires OpenLayers/Geometry/MultiLineString.js
18516  * @requires OpenLayers/Geometry/Polygon.js
18517  * @requires OpenLayers/Geometry/MultiPolygon.js
18518  */
18519
18520 /**
18521  * Class: OpenLayers.Format.GML
18522  * Read/Write GML. Create a new instance with the <OpenLayers.Format.GML>
18523  *     constructor.  Supports the GML simple features profile.
18524  * 
18525  * Inherits from:
18526  *  - <OpenLayers.Format.XML>
18527  */
18528 OpenLayers.Format.GML = OpenLayers.Class(OpenLayers.Format.XML, {
18529     
18530     /**
18531      * APIProperty: featureNS
18532      * {String} Namespace used for feature attributes.  Default is
18533      *     "http://mapserver.gis.umn.edu/mapserver".
18534      */
18535     featureNS: "http://mapserver.gis.umn.edu/mapserver",
18536     
18537     /**
18538      * APIProperty: featurePrefix
18539      * {String} Namespace alias (or prefix) for feature nodes.  Default is
18540      *     "feature".
18541      */
18542     featurePrefix: "feature",
18543     
18544     /**
18545      * APIProperty: featureName
18546      * {String} Element name for features. Default is "featureMember".
18547      */
18548     featureName: "featureMember", 
18549     
18550     /**
18551      * APIProperty: layerName
18552      * {String} Name of data layer. Default is "features".
18553      */
18554     layerName: "features",
18555     
18556     /**
18557      * APIProperty: geometryName
18558      * {String} Name of geometry element.  Defaults to "geometry".
18559      */
18560     geometryName: "geometry",
18561     
18562     /** 
18563      * APIProperty: collectionName
18564      * {String} Name of featureCollection element.
18565      */
18566     collectionName: "FeatureCollection",
18567     
18568     /**
18569      * APIProperty: gmlns
18570      * {String} GML Namespace.
18571      */
18572     gmlns: "http://www.opengis.net/gml",
18573
18574     /**
18575      * APIProperty: extractAttributes
18576      * {Boolean} Extract attributes from GML.
18577      */
18578     extractAttributes: true,
18579     
18580     /**
18581      * APIProperty: xy
18582      * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
18583      * Changing is not recommended, a new Format should be instantiated.
18584      */ 
18585     xy: true,
18586     
18587     /**
18588      * Constructor: OpenLayers.Format.GML
18589      * Create a new parser for GML.
18590      *
18591      * Parameters:
18592      * options - {Object} An optional object whose properties will be set on
18593      *     this instance.
18594      */
18595     initialize: function(options) {
18596         // compile regular expressions once instead of every time they are used
18597         this.regExes = {
18598             trimSpace: (/^\s*|\s*$/g),
18599             removeSpace: (/\s*/g),
18600             splitSpace: (/\s+/),
18601             trimComma: (/\s*,\s*/g)
18602         };
18603         OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
18604     },
18605
18606     /**
18607      * APIMethod: read
18608      * Read data from a string, and return a list of features. 
18609      * 
18610      * Parameters:
18611      * data - {String} or {DOMElement} data to read/parse.
18612      *
18613      * Returns:
18614      * {Array(<OpenLayers.Feature.Vector>)} An array of features.
18615      */
18616     read: function(data) {
18617         if(typeof data == "string") { 
18618             data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
18619         }
18620         var featureNodes = this.getElementsByTagNameNS(data.documentElement,
18621                                                        this.gmlns,
18622                                                        this.featureName);
18623         var features = [];
18624         for(var i=0; i<featureNodes.length; i++) {
18625             var feature = this.parseFeature(featureNodes[i]);
18626             if(feature) {
18627                 features.push(feature);
18628             }
18629         }
18630         return features;
18631     },
18632     
18633     /**
18634      * Method: parseFeature
18635      * This function is the core of the GML parsing code in OpenLayers.
18636      *    It creates the geometries that are then attached to the returned
18637      *    feature, and calls parseAttributes() to get attribute data out.
18638      *    
18639      * Parameters:
18640      * node - {DOMElement} A GML feature node. 
18641      */
18642     parseFeature: function(node) {
18643         // only accept one geometry per feature - look for highest "order"
18644         var order = ["MultiPolygon", "Polygon",
18645                      "MultiLineString", "LineString",
18646                      "MultiPoint", "Point", "Envelope"];
18647         // FIXME: In case we parse a feature with no geometry, but boundedBy an Envelope,
18648         // this code creates a geometry derived from the Envelope. This is not correct.
18649         var type, nodeList, geometry, parser;
18650         for(var i=0; i<order.length; ++i) {
18651             type = order[i];
18652             nodeList = this.getElementsByTagNameNS(node, this.gmlns, type);
18653             if(nodeList.length > 0) {
18654                 // only deal with first geometry of this type
18655                 parser = this.parseGeometry[type.toLowerCase()];
18656                 if(parser) {
18657                     geometry = parser.apply(this, [nodeList[0]]);
18658                     if (this.internalProjection && this.externalProjection) {
18659                         geometry.transform(this.externalProjection, 
18660                                            this.internalProjection); 
18661                     }                       
18662                 } else {
18663                     throw new TypeError("Unsupported geometry type: " + type);
18664                 }
18665                 // stop looking for different geometry types
18666                 break;
18667             }
18668         }
18669
18670         var bounds;
18671         var boxNodes = this.getElementsByTagNameNS(node, this.gmlns, "Box");
18672         for(i=0; i<boxNodes.length; ++i) {
18673             var boxNode = boxNodes[i];
18674             var box = this.parseGeometry["box"].apply(this, [boxNode]);
18675             var parentNode = boxNode.parentNode;
18676             var parentName = parentNode.localName ||
18677                              parentNode.nodeName.split(":").pop();
18678             if(parentName === "boundedBy") {
18679                 bounds = box;
18680             } else {
18681                 geometry = box.toGeometry();
18682             }
18683         }
18684         
18685         // construct feature (optionally with attributes)
18686         var attributes;
18687         if(this.extractAttributes) {
18688             attributes = this.parseAttributes(node);
18689         }
18690         var feature = new OpenLayers.Feature.Vector(geometry, attributes);
18691         feature.bounds = bounds;
18692         
18693         var firstChild = this.getFirstElementChild(node);
18694         feature.gml = {
18695             featureType: firstChild.nodeName.split(":")[1],
18696             featureNS: firstChild.namespaceURI,
18697             featureNSPrefix: firstChild.prefix
18698         };
18699         feature.type = feature.gml.featureType;
18700                 
18701         // assign fid - this can come from a "fid" or "id" attribute
18702         var childNode = node.firstChild;
18703         var fid;
18704         while(childNode) {
18705             if(childNode.nodeType == 1) {
18706                 fid = childNode.getAttribute("fid") ||
18707                       childNode.getAttribute("id");
18708                 if(fid) {
18709                     break;
18710                 }
18711             }
18712             childNode = childNode.nextSibling;
18713         }
18714         feature.fid = fid;
18715         return feature;
18716     },
18717     
18718     /**
18719      * Property: parseGeometry
18720      * Properties of this object are the functions that parse geometries based
18721      *     on their type.
18722      */
18723     parseGeometry: {
18724         
18725         /**
18726          * Method: parseGeometry.point
18727          * Given a GML node representing a point geometry, create an OpenLayers
18728          *     point geometry.
18729          *
18730          * Parameters:
18731          * node - {DOMElement} A GML node.
18732          *
18733          * Returns:
18734          * {<OpenLayers.Geometry.Point>} A point geometry.
18735          */
18736         point: function(node) {
18737             /**
18738              * Three coordinate variations to consider:
18739              * 1) <gml:pos>x y z</gml:pos>
18740              * 2) <gml:coordinates>x, y, z</gml:coordinates>
18741              * 3) <gml:coord><gml:X>x</gml:X><gml:Y>y</gml:Y></gml:coord>
18742              */
18743             var nodeList, coordString;
18744             var coords = [];
18745
18746             // look for <gml:pos>
18747             var nodeList = this.getElementsByTagNameNS(node, this.gmlns, "pos");
18748             if(nodeList.length > 0) {
18749                 coordString = nodeList[0].firstChild.nodeValue;
18750                 coordString = coordString.replace(this.regExes.trimSpace, "");
18751                 coords = coordString.split(this.regExes.splitSpace);
18752             }
18753
18754             // look for <gml:coordinates>
18755             if(coords.length == 0) {
18756                 nodeList = this.getElementsByTagNameNS(node, this.gmlns,
18757                                                        "coordinates");
18758                 if(nodeList.length > 0) {
18759                     coordString = nodeList[0].firstChild.nodeValue;
18760                     coordString = coordString.replace(this.regExes.removeSpace,
18761                                                       "");
18762                     coords = coordString.split(",");
18763                 }
18764             }
18765
18766             // look for <gml:coord>
18767             if(coords.length == 0) {
18768                 nodeList = this.getElementsByTagNameNS(node, this.gmlns,
18769                                                        "coord");
18770                 if(nodeList.length > 0) {
18771                     var xList = this.getElementsByTagNameNS(nodeList[0],
18772                                                             this.gmlns, "X");
18773                     var yList = this.getElementsByTagNameNS(nodeList[0],
18774                                                             this.gmlns, "Y");
18775                     if(xList.length > 0 && yList.length > 0) {
18776                         coords = [xList[0].firstChild.nodeValue,
18777                                   yList[0].firstChild.nodeValue];
18778                     }
18779                 }
18780             }
18781                 
18782             // preserve third dimension
18783             if(coords.length == 2) {
18784                 coords[2] = null;
18785             }
18786             
18787             if (this.xy) {
18788                 return new OpenLayers.Geometry.Point(coords[0], coords[1],
18789                                                  coords[2]);
18790             }
18791             else{
18792                 return new OpenLayers.Geometry.Point(coords[1], coords[0],
18793                                                  coords[2]);
18794             }
18795         },
18796         
18797         /**
18798          * Method: parseGeometry.multipoint
18799          * Given a GML node representing a multipoint geometry, create an
18800          *     OpenLayers multipoint geometry.
18801          *
18802          * Parameters:
18803          * node - {DOMElement} A GML node.
18804          *
18805          * Returns:
18806          * {<OpenLayers.Geometry.MultiPoint>} A multipoint geometry.
18807          */
18808         multipoint: function(node) {
18809             var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
18810                                                        "Point");
18811             var components = [];
18812             if(nodeList.length > 0) {
18813                 var point;
18814                 for(var i=0; i<nodeList.length; ++i) {
18815                     point = this.parseGeometry.point.apply(this, [nodeList[i]]);
18816                     if(point) {
18817                         components.push(point);
18818                     }
18819                 }
18820             }
18821             return new OpenLayers.Geometry.MultiPoint(components);
18822         },
18823         
18824         /**
18825          * Method: parseGeometry.linestring
18826          * Given a GML node representing a linestring geometry, create an
18827          *     OpenLayers linestring geometry.
18828          *
18829          * Parameters:
18830          * node - {DOMElement} A GML node.
18831          *
18832          * Returns:
18833          * {<OpenLayers.Geometry.LineString>} A linestring geometry.
18834          */
18835         linestring: function(node, ring) {
18836             /**
18837              * Two coordinate variations to consider:
18838              * 1) <gml:posList dimension="d">x0 y0 z0 x1 y1 z1</gml:posList>
18839              * 2) <gml:coordinates>x0, y0, z0 x1, y1, z1</gml:coordinates>
18840              */
18841             var nodeList, coordString;
18842             var coords = [];
18843             var points = [];
18844
18845             // look for <gml:posList>
18846             nodeList = this.getElementsByTagNameNS(node, this.gmlns, "posList");
18847             if(nodeList.length > 0) {
18848                 coordString = this.getChildValue(nodeList[0]);
18849                 coordString = coordString.replace(this.regExes.trimSpace, "");
18850                 coords = coordString.split(this.regExes.splitSpace);
18851                 var dim = parseInt(nodeList[0].getAttribute("dimension"));
18852                 var j, x, y, z;
18853                 for(var i=0; i<coords.length/dim; ++i) {
18854                     j = i * dim;
18855                     x = coords[j];
18856                     y = coords[j+1];
18857                     z = (dim == 2) ? null : coords[j+2];
18858                     if (this.xy) {
18859                         points.push(new OpenLayers.Geometry.Point(x, y, z));
18860                     } else {
18861                         points.push(new OpenLayers.Geometry.Point(y, x, z));
18862                     }
18863                 }
18864             }
18865
18866             // look for <gml:coordinates>
18867             if(coords.length == 0) {
18868                 nodeList = this.getElementsByTagNameNS(node, this.gmlns,
18869                                                        "coordinates");
18870                 if(nodeList.length > 0) {
18871                     coordString = this.getChildValue(nodeList[0]);
18872                     coordString = coordString.replace(this.regExes.trimSpace,
18873                                                       "");
18874                     coordString = coordString.replace(this.regExes.trimComma,
18875                                                       ",");
18876                     var pointList = coordString.split(this.regExes.splitSpace);
18877                     for(var i=0; i<pointList.length; ++i) {
18878                         coords = pointList[i].split(",");
18879                         if(coords.length == 2) {
18880                             coords[2] = null;
18881                         }
18882                         if (this.xy) {
18883                             points.push(new OpenLayers.Geometry.Point(coords[0],
18884                                                                   coords[1],
18885                                                                   coords[2]));
18886                         } else {
18887                             points.push(new OpenLayers.Geometry.Point(coords[1],
18888                                                                   coords[0],
18889                                                                   coords[2]));
18890                         }
18891                     }
18892                 }
18893             }
18894
18895             var line = null;
18896             if(points.length != 0) {
18897                 if(ring) {
18898                     line = new OpenLayers.Geometry.LinearRing(points);
18899                 } else {
18900                     line = new OpenLayers.Geometry.LineString(points);
18901                 }
18902             }
18903             return line;
18904         },
18905         
18906         /**
18907          * Method: parseGeometry.multilinestring
18908          * Given a GML node representing a multilinestring geometry, create an
18909          *     OpenLayers multilinestring geometry.
18910          *
18911          * Parameters:
18912          * node - {DOMElement} A GML node.
18913          *
18914          * Returns:
18915          * {<OpenLayers.Geometry.MultiLineString>} A multilinestring geometry.
18916          */
18917         multilinestring: function(node) {
18918             var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
18919                                                        "LineString");
18920             var components = [];
18921             if(nodeList.length > 0) {
18922                 var line;
18923                 for(var i=0; i<nodeList.length; ++i) {
18924                     line = this.parseGeometry.linestring.apply(this,
18925                                                                [nodeList[i]]);
18926                     if(line) {
18927                         components.push(line);
18928                     }
18929                 }
18930             }
18931             return new OpenLayers.Geometry.MultiLineString(components);
18932         },
18933         
18934         /**
18935          * Method: parseGeometry.polygon
18936          * Given a GML node representing a polygon geometry, create an
18937          *     OpenLayers polygon geometry.
18938          *
18939          * Parameters:
18940          * node - {DOMElement} A GML node.
18941          *
18942          * Returns:
18943          * {<OpenLayers.Geometry.Polygon>} A polygon geometry.
18944          */
18945         polygon: function(node) {
18946             var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
18947                                                        "LinearRing");
18948             var components = [];
18949             if(nodeList.length > 0) {
18950                 // this assumes exterior ring first, inner rings after
18951                 var ring;
18952                 for(var i=0; i<nodeList.length; ++i) {
18953                     ring = this.parseGeometry.linestring.apply(this,
18954                                                         [nodeList[i], true]);
18955                     if(ring) {
18956                         components.push(ring);
18957                     }
18958                 }
18959             }
18960             return new OpenLayers.Geometry.Polygon(components);
18961         },
18962         
18963         /**
18964          * Method: parseGeometry.multipolygon
18965          * Given a GML node representing a multipolygon geometry, create an
18966          *     OpenLayers multipolygon geometry.
18967          *
18968          * Parameters:
18969          * node - {DOMElement} A GML node.
18970          *
18971          * Returns:
18972          * {<OpenLayers.Geometry.MultiPolygon>} A multipolygon geometry.
18973          */
18974         multipolygon: function(node) {
18975             var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
18976                                                        "Polygon");
18977             var components = [];
18978             if(nodeList.length > 0) {
18979                 var polygon;
18980                 for(var i=0; i<nodeList.length; ++i) {
18981                     polygon = this.parseGeometry.polygon.apply(this,
18982                                                                [nodeList[i]]);
18983                     if(polygon) {
18984                         components.push(polygon);
18985                     }
18986                 }
18987             }
18988             return new OpenLayers.Geometry.MultiPolygon(components);
18989         },
18990         
18991         envelope: function(node) {
18992             var components = [];
18993             var coordString;
18994             var envelope;
18995             
18996             var lpoint = this.getElementsByTagNameNS(node, this.gmlns, "lowerCorner");
18997             if (lpoint.length > 0) {
18998                 var coords = [];
18999                 
19000                 if(lpoint.length > 0) {
19001                     coordString = lpoint[0].firstChild.nodeValue;
19002                     coordString = coordString.replace(this.regExes.trimSpace, "");
19003                     coords = coordString.split(this.regExes.splitSpace);
19004                 }
19005                 
19006                 if(coords.length == 2) {
19007                     coords[2] = null;
19008                 }
19009                 if (this.xy) {
19010                     var lowerPoint = new OpenLayers.Geometry.Point(coords[0], coords[1],coords[2]);
19011                 } else {
19012                     var lowerPoint = new OpenLayers.Geometry.Point(coords[1], coords[0],coords[2]);
19013                 }
19014             }
19015             
19016             var upoint = this.getElementsByTagNameNS(node, this.gmlns, "upperCorner");
19017             if (upoint.length > 0) {
19018                 var coords = [];
19019                 
19020                 if(upoint.length > 0) {
19021                     coordString = upoint[0].firstChild.nodeValue;
19022                     coordString = coordString.replace(this.regExes.trimSpace, "");
19023                     coords = coordString.split(this.regExes.splitSpace);
19024                 }
19025                 
19026                 if(coords.length == 2) {
19027                     coords[2] = null;
19028                 }
19029                 if (this.xy) {
19030                     var upperPoint = new OpenLayers.Geometry.Point(coords[0], coords[1],coords[2]);
19031                 } else {
19032                     var upperPoint = new OpenLayers.Geometry.Point(coords[1], coords[0],coords[2]);
19033                 }
19034             }
19035             
19036             if (lowerPoint && upperPoint) {
19037                 components.push(new OpenLayers.Geometry.Point(lowerPoint.x, lowerPoint.y));
19038                 components.push(new OpenLayers.Geometry.Point(upperPoint.x, lowerPoint.y));
19039                 components.push(new OpenLayers.Geometry.Point(upperPoint.x, upperPoint.y));
19040                 components.push(new OpenLayers.Geometry.Point(lowerPoint.x, upperPoint.y));
19041                 components.push(new OpenLayers.Geometry.Point(lowerPoint.x, lowerPoint.y));
19042                 
19043                 var ring = new OpenLayers.Geometry.LinearRing(components);
19044                 envelope = new OpenLayers.Geometry.Polygon([ring]);
19045             }
19046             return envelope; 
19047         },
19048
19049         /**
19050          * Method: parseGeometry.box
19051          * Given a GML node representing a box geometry, create an
19052          *     OpenLayers.Bounds.
19053          *
19054          * Parameters:
19055          * node - {DOMElement} A GML node.
19056          *
19057          * Returns:
19058          * {<OpenLayers.Bounds>} A bounds representing the box.
19059          */
19060         box: function(node) {
19061             var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
19062                                                    "coordinates");
19063             var coordString;
19064             var coords, beginPoint = null, endPoint = null;
19065             if (nodeList.length > 0) {
19066                 coordString = nodeList[0].firstChild.nodeValue;
19067                 coords = coordString.split(" ");
19068                 if (coords.length == 2) {
19069                     beginPoint = coords[0].split(",");
19070                     endPoint = coords[1].split(",");
19071                 }
19072             }
19073             if (beginPoint !== null && endPoint !== null) {
19074                 return new OpenLayers.Bounds(parseFloat(beginPoint[0]),
19075                     parseFloat(beginPoint[1]),
19076                     parseFloat(endPoint[0]),
19077                     parseFloat(endPoint[1]) );
19078             }
19079         }
19080         
19081     },
19082     
19083     /**
19084      * Method: parseAttributes
19085      *
19086      * Parameters:
19087      * node - {DOMElement}
19088      *
19089      * Returns:
19090      * {Object} An attributes object.
19091      */
19092     parseAttributes: function(node) {
19093         var attributes = {};
19094         // assume attributes are children of the first type 1 child
19095         var childNode = node.firstChild;
19096         var children, i, child, grandchildren, grandchild, name, value;
19097         while(childNode) {
19098             if(childNode.nodeType == 1) {
19099                 // attributes are type 1 children with one type 3 child
19100                 children = childNode.childNodes;
19101                 for(i=0; i<children.length; ++i) {
19102                     child = children[i];
19103                     if(child.nodeType == 1) {
19104                         grandchildren = child.childNodes;
19105                         if(grandchildren.length == 1) {
19106                             grandchild = grandchildren[0];
19107                             if(grandchild.nodeType == 3 ||
19108                                grandchild.nodeType == 4) {
19109                                 name = (child.prefix) ?
19110                                         child.nodeName.split(":")[1] :
19111                                         child.nodeName;
19112                                 value = grandchild.nodeValue.replace(
19113                                                 this.regExes.trimSpace, "");
19114                                 attributes[name] = value;
19115                             }
19116                         } else {
19117                             // If child has no childNodes (grandchildren),
19118                             // set an attribute with null value.
19119                             // e.g. <prefix:fieldname/> becomes
19120                             // {fieldname: null}
19121                             attributes[child.nodeName.split(":").pop()] = null;
19122                         }
19123                     }
19124                 }
19125                 break;
19126             }
19127             childNode = childNode.nextSibling;
19128         }
19129         return attributes;
19130     },
19131     
19132     /**
19133      * APIMethod: write
19134      * Generate a GML document string given a list of features. 
19135      * 
19136      * Parameters:
19137      * features - {Array(<OpenLayers.Feature.Vector>)} List of features to
19138      *     serialize into a string.
19139      *
19140      * Returns:
19141      * {String} A string representing the GML document.
19142      */
19143     write: function(features) {
19144         if(!(OpenLayers.Util.isArray(features))) {
19145             features = [features];
19146         }
19147         var gml = this.createElementNS("http://www.opengis.net/wfs",
19148                                        "wfs:" + this.collectionName);
19149         for(var i=0; i<features.length; i++) {
19150             gml.appendChild(this.createFeatureXML(features[i]));
19151         }
19152         return OpenLayers.Format.XML.prototype.write.apply(this, [gml]);
19153     },
19154
19155     /** 
19156      * Method: createFeatureXML
19157      * Accept an OpenLayers.Feature.Vector, and build a GML node for it.
19158      *
19159      * Parameters:
19160      * feature - {<OpenLayers.Feature.Vector>} The feature to be built as GML.
19161      *
19162      * Returns:
19163      * {DOMElement} A node reprensting the feature in GML.
19164      */
19165     createFeatureXML: function(feature) {
19166         var geometry = feature.geometry;
19167         var geometryNode = this.buildGeometryNode(geometry);
19168         var geomContainer = this.createElementNS(this.featureNS,
19169                                                  this.featurePrefix + ":" +
19170                                                  this.geometryName);
19171         geomContainer.appendChild(geometryNode);
19172         var featureNode = this.createElementNS(this.gmlns,
19173                                                "gml:" + this.featureName);
19174         var featureContainer = this.createElementNS(this.featureNS,
19175                                                     this.featurePrefix + ":" +
19176                                                     this.layerName);
19177         var fid = feature.fid || feature.id;
19178         featureContainer.setAttribute("fid", fid);
19179         featureContainer.appendChild(geomContainer);
19180         for(var attr in feature.attributes) {
19181             var attrText = this.createTextNode(feature.attributes[attr]); 
19182             var nodename = attr.substring(attr.lastIndexOf(":") + 1);
19183             var attrContainer = this.createElementNS(this.featureNS,
19184                                                      this.featurePrefix + ":" +
19185                                                      nodename);
19186             attrContainer.appendChild(attrText);
19187             featureContainer.appendChild(attrContainer);
19188         }    
19189         featureNode.appendChild(featureContainer);
19190         return featureNode;
19191     },
19192     
19193     /**
19194      * APIMethod: buildGeometryNode
19195      */
19196     buildGeometryNode: function(geometry) {
19197         if (this.externalProjection && this.internalProjection) {
19198             geometry = geometry.clone();
19199             geometry.transform(this.internalProjection, 
19200                                this.externalProjection);
19201         }    
19202         var className = geometry.CLASS_NAME;
19203         var type = className.substring(className.lastIndexOf(".") + 1);
19204         var builder = this.buildGeometry[type.toLowerCase()];
19205         return builder.apply(this, [geometry]);
19206     },
19207
19208     /**
19209      * Property: buildGeometry
19210      * Object containing methods to do the actual geometry node building
19211      *     based on geometry type.
19212      */
19213     buildGeometry: {
19214         // TBD retrieve the srs from layer
19215         // srsName is non-standard, so not including it until it's right.
19216         // gml.setAttribute("srsName",
19217         //                  "http://www.opengis.net/gml/srs/epsg.xml#4326");
19218
19219         /**
19220          * Method: buildGeometry.point
19221          * Given an OpenLayers point geometry, create a GML point.
19222          *
19223          * Parameters:
19224          * geometry - {<OpenLayers.Geometry.Point>} A point geometry.
19225          *
19226          * Returns:
19227          * {DOMElement} A GML point node.
19228          */
19229         point: function(geometry) {
19230             var gml = this.createElementNS(this.gmlns, "gml:Point");
19231             gml.appendChild(this.buildCoordinatesNode(geometry));
19232             return gml;
19233         },
19234         
19235         /**
19236          * Method: buildGeometry.multipoint
19237          * Given an OpenLayers multipoint geometry, create a GML multipoint.
19238          *
19239          * Parameters:
19240          * geometry - {<OpenLayers.Geometry.MultiPoint>} A multipoint geometry.
19241          *
19242          * Returns:
19243          * {DOMElement} A GML multipoint node.
19244          */
19245         multipoint: function(geometry) {
19246             var gml = this.createElementNS(this.gmlns, "gml:MultiPoint");
19247             var points = geometry.components;
19248             var pointMember, pointGeom;
19249             for(var i=0; i<points.length; i++) { 
19250                 pointMember = this.createElementNS(this.gmlns,
19251                                                    "gml:pointMember");
19252                 pointGeom = this.buildGeometry.point.apply(this,
19253                                                                [points[i]]);
19254                 pointMember.appendChild(pointGeom);
19255                 gml.appendChild(pointMember);
19256             }
19257             return gml;            
19258         },
19259         
19260         /**
19261          * Method: buildGeometry.linestring
19262          * Given an OpenLayers linestring geometry, create a GML linestring.
19263          *
19264          * Parameters:
19265          * geometry - {<OpenLayers.Geometry.LineString>} A linestring geometry.
19266          *
19267          * Returns:
19268          * {DOMElement} A GML linestring node.
19269          */
19270         linestring: function(geometry) {
19271             var gml = this.createElementNS(this.gmlns, "gml:LineString");
19272             gml.appendChild(this.buildCoordinatesNode(geometry));
19273             return gml;
19274         },
19275         
19276         /**
19277          * Method: buildGeometry.multilinestring
19278          * Given an OpenLayers multilinestring geometry, create a GML
19279          *     multilinestring.
19280          *
19281          * Parameters:
19282          * geometry - {<OpenLayers.Geometry.MultiLineString>} A multilinestring
19283          *     geometry.
19284          *
19285          * Returns:
19286          * {DOMElement} A GML multilinestring node.
19287          */
19288         multilinestring: function(geometry) {
19289             var gml = this.createElementNS(this.gmlns, "gml:MultiLineString");
19290             var lines = geometry.components;
19291             var lineMember, lineGeom;
19292             for(var i=0; i<lines.length; ++i) {
19293                 lineMember = this.createElementNS(this.gmlns,
19294                                                   "gml:lineStringMember");
19295                 lineGeom = this.buildGeometry.linestring.apply(this,
19296                                                                    [lines[i]]);
19297                 lineMember.appendChild(lineGeom);
19298                 gml.appendChild(lineMember);
19299             }
19300             return gml;
19301         },
19302         
19303         /**
19304          * Method: buildGeometry.linearring
19305          * Given an OpenLayers linearring geometry, create a GML linearring.
19306          *
19307          * Parameters:
19308          * geometry - {<OpenLayers.Geometry.LinearRing>} A linearring geometry.
19309          *
19310          * Returns:
19311          * {DOMElement} A GML linearring node.
19312          */
19313         linearring: function(geometry) {
19314             var gml = this.createElementNS(this.gmlns, "gml:LinearRing");
19315             gml.appendChild(this.buildCoordinatesNode(geometry));
19316             return gml;
19317         },
19318         
19319         /**
19320          * Method: buildGeometry.polygon
19321          * Given an OpenLayers polygon geometry, create a GML polygon.
19322          *
19323          * Parameters:
19324          * geometry - {<OpenLayers.Geometry.Polygon>} A polygon geometry.
19325          *
19326          * Returns:
19327          * {DOMElement} A GML polygon node.
19328          */
19329         polygon: function(geometry) {
19330             var gml = this.createElementNS(this.gmlns, "gml:Polygon");
19331             var rings = geometry.components;
19332             var ringMember, ringGeom, type;
19333             for(var i=0; i<rings.length; ++i) {
19334                 type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
19335                 ringMember = this.createElementNS(this.gmlns,
19336                                                   "gml:" + type);
19337                 ringGeom = this.buildGeometry.linearring.apply(this,
19338                                                                    [rings[i]]);
19339                 ringMember.appendChild(ringGeom);
19340                 gml.appendChild(ringMember);
19341             }
19342             return gml;
19343         },
19344         
19345         /**
19346          * Method: buildGeometry.multipolygon
19347          * Given an OpenLayers multipolygon geometry, create a GML multipolygon.
19348          *
19349          * Parameters:
19350          * geometry - {<OpenLayers.Geometry.MultiPolygon>} A multipolygon
19351          *     geometry.
19352          *
19353          * Returns:
19354          * {DOMElement} A GML multipolygon node.
19355          */
19356         multipolygon: function(geometry) {
19357             var gml = this.createElementNS(this.gmlns, "gml:MultiPolygon");
19358             var polys = geometry.components;
19359             var polyMember, polyGeom;
19360             for(var i=0; i<polys.length; ++i) {
19361                 polyMember = this.createElementNS(this.gmlns,
19362                                                   "gml:polygonMember");
19363                 polyGeom = this.buildGeometry.polygon.apply(this,
19364                                                                 [polys[i]]);
19365                 polyMember.appendChild(polyGeom);
19366                 gml.appendChild(polyMember);
19367             }
19368             return gml;
19369
19370         },
19371  
19372         /**
19373          * Method: buildGeometry.bounds
19374          * Given an OpenLayers bounds, create a GML box.
19375          *
19376          * Parameters:
19377          * bounds - {<OpenLayers.Geometry.Bounds>} A bounds object.
19378          *
19379          * Returns:
19380          * {DOMElement} A GML box node.
19381          */
19382         bounds: function(bounds) {
19383             var gml = this.createElementNS(this.gmlns, "gml:Box");
19384             gml.appendChild(this.buildCoordinatesNode(bounds));
19385             return gml;
19386         }
19387     },
19388
19389     /**
19390      * Method: buildCoordinates
19391      * builds the coordinates XmlNode
19392      * (code)
19393      * <gml:coordinates decimal="." cs="," ts=" ">...</gml:coordinates>
19394      * (end)
19395      *
19396      * Parameters: 
19397      * geometry - {<OpenLayers.Geometry>} 
19398      *
19399      * Returns:
19400      * {XmlNode} created xmlNode
19401      */
19402     buildCoordinatesNode: function(geometry) {
19403         var coordinatesNode = this.createElementNS(this.gmlns,
19404                                                    "gml:coordinates");
19405         coordinatesNode.setAttribute("decimal", ".");
19406         coordinatesNode.setAttribute("cs", ",");
19407         coordinatesNode.setAttribute("ts", " ");
19408
19409         var parts = [];
19410
19411         if(geometry instanceof OpenLayers.Bounds){
19412             parts.push(geometry.left + "," + geometry.bottom);
19413             parts.push(geometry.right + "," + geometry.top);
19414         } else {
19415             var points = (geometry.components) ? geometry.components : [geometry];
19416             for(var i=0; i<points.length; i++) {
19417                 parts.push(points[i].x + "," + points[i].y);                
19418             }            
19419         }
19420
19421         var txtNode = this.createTextNode(parts.join(" "));
19422         coordinatesNode.appendChild(txtNode);
19423         
19424         return coordinatesNode;
19425     },
19426
19427     CLASS_NAME: "OpenLayers.Format.GML" 
19428 });
19429 /* ======================================================================
19430     OpenLayers/Format/GML/Base.js
19431    ====================================================================== */
19432
19433 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
19434  * full list of contributors). Published under the 2-clause BSD license.
19435  * See license.txt in the OpenLayers distribution or repository for the
19436  * full text of the license. */
19437
19438 /**
19439  * @requires OpenLayers/Format/XML.js
19440  * @requires OpenLayers/Format/GML.js
19441  */
19442
19443 /**
19444  * Though required in the full build, if the GML format is excluded, we set
19445  * the namespace here.
19446  */
19447 if(!OpenLayers.Format.GML) {
19448     OpenLayers.Format.GML = {};
19449 }
19450
19451 /**
19452  * Class: OpenLayers.Format.GML.Base
19453  * Superclass for GML parsers.
19454  *
19455  * Inherits from:
19456  *  - <OpenLayers.Format.XML>
19457  */
19458 OpenLayers.Format.GML.Base = OpenLayers.Class(OpenLayers.Format.XML, {
19459
19460     /**
19461      * Property: namespaces
19462      * {Object} Mapping of namespace aliases to namespace URIs.
19463      */
19464     namespaces: {
19465         gml: "http://www.opengis.net/gml",
19466         xlink: "http://www.w3.org/1999/xlink",
19467         xsi: "http://www.w3.org/2001/XMLSchema-instance",
19468         wfs: "http://www.opengis.net/wfs" // this is a convenience for reading wfs:FeatureCollection
19469     },
19470
19471     /**
19472      * Property: defaultPrefix
19473      */
19474     defaultPrefix: "gml",
19475
19476     /**
19477      * Property: schemaLocation
19478      * {String} Schema location for a particular minor version.
19479      */
19480     schemaLocation: null,
19481
19482     /**
19483      * APIProperty: featureType
19484      * {Array(String) or String} The local (without prefix) feature typeName(s).
19485      */
19486     featureType: null,
19487
19488     /**
19489      * APIProperty: featureNS
19490      * {String} The feature namespace.  Must be set in the options at
19491      *     construction.
19492      */
19493     featureNS: null,
19494
19495     /**
19496      * APIProperty: featurePrefix
19497      * {String} Namespace alias (or prefix) for feature nodes.  Default is
19498      *     "feature".
19499      */
19500     featurePrefix: "feature",
19501
19502     /**
19503      * APIProperty: geometry
19504      * {String} Name of geometry element.  Defaults to "geometry". If null, it
19505      * will be set on <read> when the first geometry is parsed.
19506      */
19507     geometryName: "geometry",
19508
19509     /**
19510      * APIProperty: extractAttributes
19511      * {Boolean} Extract attributes from GML.  Default is true.
19512      */
19513     extractAttributes: true,
19514
19515     /**
19516      * APIProperty: srsName
19517      * {String} URI for spatial reference system.  This is optional for
19518      *     single part geometries and mandatory for collections and multis.
19519      *     If set, the srsName attribute will be written for all geometries.
19520      *     Default is null.
19521      */
19522     srsName: null,
19523
19524     /**
19525      * APIProperty: xy
19526      * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
19527      * Changing is not recommended, a new Format should be instantiated.
19528      */
19529     xy: true,
19530
19531     /**
19532      * Property: geometryTypes
19533      * {Object} Maps OpenLayers geometry class names to GML element names.
19534      *     Use <setGeometryTypes> before accessing this property.
19535      */
19536     geometryTypes: null,
19537
19538     /**
19539      * Property: singleFeatureType
19540      * {Boolean} True if there is only 1 featureType, and not an array
19541      *     of featuretypes.
19542      */
19543     singleFeatureType: null,
19544
19545     /**
19546      * Property: autoConfig
19547      * {Boolean} Indicates if the format was configured without a <featureNS>,
19548      * but auto-configured <featureNS> and <featureType> during read.
19549      * Subclasses making use of <featureType> auto-configuration should make
19550      * the first call to the <readNode> method (usually in the read method)
19551      * with true as 3rd argument, so the auto-configured featureType can be
19552      * reset and the format can be reused for subsequent reads with data from
19553      * different featureTypes. Set to false after read if you want to keep the
19554      * auto-configured values.
19555      */
19556
19557     /**
19558      * Property: regExes
19559      * Compiled regular expressions for manipulating strings.
19560      */
19561     regExes: {
19562         trimSpace: (/^\s*|\s*$/g),
19563         removeSpace: (/\s*/g),
19564         splitSpace: (/\s+/),
19565         trimComma: (/\s*,\s*/g),
19566         featureMember: (/^(.*:)?featureMembers?$/)
19567     },
19568
19569     /**
19570      * Constructor: OpenLayers.Format.GML.Base
19571      * Instances of this class are not created directly.  Use the
19572      *     <OpenLayers.Format.GML.v2> or <OpenLayers.Format.GML.v3> constructor
19573      *     instead.
19574      *
19575      * Parameters:
19576      * options - {Object} An optional object whose properties will be set on
19577      *     this instance.
19578      *
19579      * Valid options properties:
19580      * featureType - {Array(String) or String} Local (without prefix) feature
19581      *     typeName(s) (required for write).
19582      * featureNS - {String} Feature namespace (required for write).
19583      * geometryName - {String} Geometry element name (required for write).
19584      */
19585     initialize: function(options) {
19586         OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
19587         this.setGeometryTypes();
19588         if(options && options.featureNS) {
19589             this.setNamespace(this.featurePrefix, options.featureNS);
19590         }
19591         this.singleFeatureType = !options || (typeof options.featureType === "string");
19592     },
19593
19594     /**
19595      * Method: read
19596      *
19597      * Parameters:
19598      * data - {DOMElement} A gml:featureMember element, a gml:featureMembers
19599      *     element, or an element containing either of the above at any level.
19600      *
19601      * Returns:
19602      * {Array(<OpenLayers.Feature.Vector>)} An array of features.
19603      */
19604     read: function(data) {
19605         if(typeof data == "string") {
19606             data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
19607         }
19608         if(data && data.nodeType == 9) {
19609             data = data.documentElement;
19610         }
19611         var features = [];
19612         this.readNode(data, {features: features}, true);
19613         if(features.length == 0) {
19614             // look for gml:featureMember elements
19615             var elements = this.getElementsByTagNameNS(
19616                 data, this.namespaces.gml, "featureMember"
19617             );
19618             if(elements.length) {
19619                 for(var i=0, len=elements.length; i<len; ++i) {
19620                     this.readNode(elements[i], {features: features}, true);
19621                 }
19622             } else {
19623                 // look for gml:featureMembers elements (this is v3, but does no harm here)
19624                 var elements = this.getElementsByTagNameNS(
19625                     data, this.namespaces.gml, "featureMembers"
19626                 );
19627                 if(elements.length) {
19628                     // there can be only one
19629                     this.readNode(elements[0], {features: features}, true);
19630                 }
19631             }
19632         }
19633         return features;
19634     },
19635
19636     /**
19637      * Method: readNode
19638      * Shorthand for applying one of the named readers given the node
19639      *     namespace and local name.  Readers take two args (node, obj) and
19640      *     generally extend or modify the second.
19641      *
19642      * Parameters:
19643      * node - {DOMElement} The node to be read (required).
19644      * obj - {Object} The object to be modified (optional).
19645      * first - {Boolean} Should be set to true for the first node read. This
19646      *     is usually the readNode call in the read method. Without this being
19647      *     set, auto-configured properties will stick on subsequent reads.
19648      *
19649      * Returns:
19650      * {Object} The input object, modified (or a new one if none was provided).
19651      */
19652     readNode: function(node, obj, first) {
19653         // on subsequent calls of format.read(), we want to reset auto-
19654         // configured properties and auto-configure again.
19655         if (first === true && this.autoConfig === true) {
19656             this.featureType = null;
19657             delete this.namespaceAlias[this.featureNS];
19658             delete this.namespaces["feature"];
19659             this.featureNS = null;
19660         }
19661         // featureType auto-configuration
19662         if (!this.featureNS && (!(node.prefix in this.namespaces) &&
19663                 node.parentNode.namespaceURI == this.namespaces["gml"] &&
19664                 this.regExes.featureMember.test(node.parentNode.nodeName))) {
19665             this.featureType = node.nodeName.split(":").pop();
19666             this.setNamespace("feature", node.namespaceURI);
19667             this.featureNS = node.namespaceURI;
19668             this.autoConfig = true;
19669         }
19670         return OpenLayers.Format.XML.prototype.readNode.apply(this, [node, obj]);
19671     },
19672
19673     /**
19674      * Property: readers
19675      * Contains public functions, grouped by namespace prefix, that will
19676      *     be applied when a namespaced node is found matching the function
19677      *     name.  The function will be applied in the scope of this parser
19678      *     with two arguments: the node being read and a context object passed
19679      *     from the parent.
19680      */
19681     readers: {
19682         "gml": {
19683             "_inherit": function(node, obj, container) {
19684                 // To be implemented by version specific parsers
19685             },
19686             "featureMember": function(node, obj) {
19687                 this.readChildNodes(node, obj);
19688             },
19689             "featureMembers": function(node, obj) {
19690                 this.readChildNodes(node, obj);
19691             },
19692             "name": function(node, obj) {
19693                 obj.name = this.getChildValue(node);
19694             },
19695             "boundedBy": function(node, obj) {
19696                 var container = {};
19697                 this.readChildNodes(node, container);
19698                 if(container.components && container.components.length > 0) {
19699                     obj.bounds = container.components[0];
19700                 }
19701             },
19702             "Point": function(node, container) {
19703                 var obj = {points: []};
19704                 this.readChildNodes(node, obj);
19705                 if(!container.components) {
19706                     container.components = [];
19707                 }
19708                 container.components.push(obj.points[0]);
19709             },
19710             "coordinates": function(node, obj) {
19711                 var str = this.getChildValue(node).replace(
19712                     this.regExes.trimSpace, ""
19713                 );
19714                 str = str.replace(this.regExes.trimComma, ",");
19715                 var pointList = str.split(this.regExes.splitSpace);
19716                 var coords;
19717                 var numPoints = pointList.length;
19718                 var points = new Array(numPoints);
19719                 for(var i=0; i<numPoints; ++i) {
19720                     coords = pointList[i].split(",");
19721                     if (this.xy) {
19722                         points[i] = new OpenLayers.Geometry.Point(
19723                             coords[0], coords[1], coords[2]
19724                         );
19725                     } else {
19726                         points[i] = new OpenLayers.Geometry.Point(
19727                             coords[1], coords[0], coords[2]
19728                         );
19729                     }
19730                 }
19731                 obj.points = points;
19732             },
19733             "coord": function(node, obj) {
19734                 var coord = {};
19735                 this.readChildNodes(node, coord);
19736                 if(!obj.points) {
19737                     obj.points = [];
19738                 }
19739                 obj.points.push(new OpenLayers.Geometry.Point(
19740                     coord.x, coord.y, coord.z
19741                 ));
19742             },
19743             "X": function(node, coord) {
19744                 coord.x = this.getChildValue(node);
19745             },
19746             "Y": function(node, coord) {
19747                 coord.y = this.getChildValue(node);
19748             },
19749             "Z": function(node, coord) {
19750                 coord.z = this.getChildValue(node);
19751             },
19752             "MultiPoint": function(node, container) {
19753                 var obj = {components: []};
19754                 this.readers.gml._inherit.apply(this, [node, obj, container]);
19755                 this.readChildNodes(node, obj);
19756                 container.components = [
19757                     new OpenLayers.Geometry.MultiPoint(obj.components)
19758                 ];
19759             },
19760             "pointMember": function(node, obj) {
19761                 this.readChildNodes(node, obj);
19762             },
19763             "LineString": function(node, container) {
19764                 var obj = {};
19765                 this.readers.gml._inherit.apply(this, [node, obj, container]);
19766                 this.readChildNodes(node, obj);
19767                 if(!container.components) {
19768                     container.components = [];
19769                 }
19770                 container.components.push(
19771                     new OpenLayers.Geometry.LineString(obj.points)
19772                 );
19773             },
19774             "MultiLineString": function(node, container) {
19775                 var obj = {components: []};
19776                 this.readers.gml._inherit.apply(this, [node, obj, container]);
19777                 this.readChildNodes(node, obj);
19778                 container.components = [
19779                     new OpenLayers.Geometry.MultiLineString(obj.components)
19780                 ];
19781             },
19782             "lineStringMember": function(node, obj) {
19783                 this.readChildNodes(node, obj);
19784             },
19785             "Polygon": function(node, container) {
19786                 var obj = {outer: null, inner: []};
19787                 this.readers.gml._inherit.apply(this, [node, obj, container]);
19788                 this.readChildNodes(node, obj);
19789                 obj.inner.unshift(obj.outer);
19790                 if(!container.components) {
19791                     container.components = [];
19792                 }
19793                 container.components.push(
19794                     new OpenLayers.Geometry.Polygon(obj.inner)
19795                 );
19796             },
19797             "LinearRing": function(node, obj) {
19798                 var container = {};
19799                 this.readers.gml._inherit.apply(this, [node, container]);
19800                 this.readChildNodes(node, container);
19801                 obj.components = [new OpenLayers.Geometry.LinearRing(
19802                     container.points
19803                 )];
19804             },
19805             "MultiPolygon": function(node, container) {
19806                 var obj = {components: []};
19807                 this.readers.gml._inherit.apply(this, [node, obj, container]);
19808                 this.readChildNodes(node, obj);
19809                 container.components = [
19810                     new OpenLayers.Geometry.MultiPolygon(obj.components)
19811                 ];
19812             },
19813             "polygonMember": function(node, obj) {
19814                 this.readChildNodes(node, obj);
19815             },
19816             "GeometryCollection": function(node, container) {
19817                 var obj = {components: []};
19818                 this.readers.gml._inherit.apply(this, [node, obj, container]);
19819                 this.readChildNodes(node, obj);
19820                 container.components = [
19821                     new OpenLayers.Geometry.Collection(obj.components)
19822                 ];
19823             },
19824             "geometryMember": function(node, obj) {
19825                 this.readChildNodes(node, obj);
19826             }
19827         },
19828         "feature": {
19829             "*": function(node, obj) {
19830                 // The node can either be named like the featureType, or it
19831                 // can be a child of the feature:featureType.  Children can be
19832                 // geometry or attributes.
19833                 var name;
19834                 var local = node.localName || node.nodeName.split(":").pop();
19835                 // Since an attribute can have the same name as the feature type
19836                 // we only want to read the node as a feature if the parent
19837                 // node can have feature nodes as children.  In this case, the
19838                 // obj.features property is set.
19839                 if (obj.features) {
19840                     if (!this.singleFeatureType &&
19841                         (OpenLayers.Util.indexOf(this.featureType, local) !== -1)) {
19842                         name = "_typeName";
19843                     } else if(local === this.featureType) {
19844                         name = "_typeName";
19845                     }
19846                 } else {
19847                     // Assume attribute elements have one child node and that the child
19848                     // is a text node.  Otherwise assume it is a geometry node.
19849                     if(node.childNodes.length == 0 ||
19850                        (node.childNodes.length == 1 && node.firstChild.nodeType == 3)) {
19851                         if(this.extractAttributes) {
19852                             name = "_attribute";
19853                         }
19854                     } else {
19855                         name = "_geometry";
19856                     }
19857                 }
19858                 if(name) {
19859                     this.readers.feature[name].apply(this, [node, obj]);
19860                 }
19861             },
19862             "_typeName": function(node, obj) {
19863                 var container = {components: [], attributes: {}};
19864                 this.readChildNodes(node, container);
19865                 // look for common gml namespaced elements
19866                 if(container.name) {
19867                     container.attributes.name = container.name;
19868                 }
19869                 var feature = new OpenLayers.Feature.Vector(
19870                     container.components[0], container.attributes
19871                 );
19872                 if (!this.singleFeatureType) {
19873                     feature.type = node.nodeName.split(":").pop();
19874                     feature.namespace = node.namespaceURI;
19875                 }
19876                 var fid = node.getAttribute("fid") ||
19877                     this.getAttributeNS(node, this.namespaces["gml"], "id");
19878                 if(fid) {
19879                     feature.fid = fid;
19880                 }
19881                 if(this.internalProjection && this.externalProjection &&
19882                    feature.geometry) {
19883                     feature.geometry.transform(
19884                         this.externalProjection, this.internalProjection
19885                     );
19886                 }
19887                 if(container.bounds) {
19888                     feature.bounds = container.bounds;
19889                 }
19890                 obj.features.push(feature);
19891             },
19892             "_geometry": function(node, obj) {
19893                 if (!this.geometryName) {
19894                     this.geometryName = node.nodeName.split(":").pop();
19895                 }
19896                 this.readChildNodes(node, obj);
19897             },
19898             "_attribute": function(node, obj) {
19899                 var local = node.localName || node.nodeName.split(":").pop();
19900                 var value = this.getChildValue(node);
19901                 obj.attributes[local] = value;
19902             }
19903         },
19904         "wfs": {
19905             "FeatureCollection": function(node, obj) {
19906                 this.readChildNodes(node, obj);
19907             }
19908         }
19909     },
19910
19911     /**
19912      * Method: write
19913      *
19914      * Parameters:
19915      * features - {Array(<OpenLayers.Feature.Vector>) | OpenLayers.Feature.Vector}
19916      *     An array of features or a single feature.
19917      *
19918      * Returns:
19919      * {String} Given an array of features, a doc with a gml:featureMembers
19920      *     element will be returned.  Given a single feature, a doc with a
19921      *     gml:featureMember element will be returned.
19922      */
19923     write: function(features) {
19924         var name;
19925         if(OpenLayers.Util.isArray(features)) {
19926             name = "featureMembers";
19927         } else {
19928             name = "featureMember";
19929         }
19930         var root = this.writeNode("gml:" + name, features);
19931         this.setAttributeNS(
19932             root, this.namespaces["xsi"],
19933             "xsi:schemaLocation", this.schemaLocation
19934         );
19935
19936         return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
19937     },
19938
19939     /**
19940      * Property: writers
19941      * As a compliment to the readers property, this structure contains public
19942      *     writing functions grouped by namespace alias and named like the
19943      *     node names they produce.
19944      */
19945     writers: {
19946         "gml": {
19947             "featureMember": function(feature) {
19948                 var node = this.createElementNSPlus("gml:featureMember");
19949                 this.writeNode("feature:_typeName", feature, node);
19950                 return node;
19951             },
19952             "MultiPoint": function(geometry) {
19953                 var node = this.createElementNSPlus("gml:MultiPoint");
19954                 var components = geometry.components || [geometry];
19955                 for(var i=0, ii=components.length; i<ii; ++i) {
19956                     this.writeNode("pointMember", components[i], node);
19957                 }
19958                 return node;
19959             },
19960             "pointMember": function(geometry) {
19961                 var node = this.createElementNSPlus("gml:pointMember");
19962                 this.writeNode("Point", geometry, node);
19963                 return node;
19964             },
19965             "MultiLineString": function(geometry) {
19966                 var node = this.createElementNSPlus("gml:MultiLineString");
19967                 var components = geometry.components || [geometry];
19968                 for(var i=0, ii=components.length; i<ii; ++i) {
19969                     this.writeNode("lineStringMember", components[i], node);
19970                 }
19971                 return node;
19972             },
19973             "lineStringMember": function(geometry) {
19974                 var node = this.createElementNSPlus("gml:lineStringMember");
19975                 this.writeNode("LineString", geometry, node);
19976                 return node;
19977             },
19978             "MultiPolygon": function(geometry) {
19979                 var node = this.createElementNSPlus("gml:MultiPolygon");
19980                 var components = geometry.components || [geometry];
19981                 for(var i=0, ii=components.length; i<ii; ++i) {
19982                     this.writeNode(
19983                         "polygonMember", components[i], node
19984                     );
19985                 }
19986                 return node;
19987             },
19988             "polygonMember": function(geometry) {
19989                 var node = this.createElementNSPlus("gml:polygonMember");
19990                 this.writeNode("Polygon", geometry, node);
19991                 return node;
19992             },
19993             "GeometryCollection": function(geometry) {
19994                 var node = this.createElementNSPlus("gml:GeometryCollection");
19995                 for(var i=0, len=geometry.components.length; i<len; ++i) {
19996                     this.writeNode("geometryMember", geometry.components[i], node);
19997                 }
19998                 return node;
19999             },
20000             "geometryMember": function(geometry) {
20001                 var node = this.createElementNSPlus("gml:geometryMember");
20002                 var child = this.writeNode("feature:_geometry", geometry);
20003                 node.appendChild(child.firstChild);
20004                 return node;
20005             }
20006         },
20007         "feature": {
20008             "_typeName": function(feature) {
20009                 var node = this.createElementNSPlus(this.featurePrefix + ":" + this.featureType, {
20010                     attributes: {fid: feature.fid}
20011                 });
20012                 if(feature.geometry) {
20013                     this.writeNode("feature:_geometry", feature.geometry, node);
20014                 }
20015                 for(var name in feature.attributes) {
20016                     var value = feature.attributes[name];
20017                     if(value != null) {
20018                         this.writeNode(
20019                             "feature:_attribute",
20020                             {name: name, value: value}, node
20021                         );
20022                     }
20023                 }
20024                 return node;
20025             },
20026             "_geometry": function(geometry) {
20027                 if(this.externalProjection && this.internalProjection) {
20028                     geometry = geometry.clone().transform(
20029                         this.internalProjection, this.externalProjection
20030                     );
20031                 }
20032                 var node = this.createElementNSPlus(
20033                     this.featurePrefix + ":" + this.geometryName
20034                 );
20035                 var type = this.geometryTypes[geometry.CLASS_NAME];
20036                 var child = this.writeNode("gml:" + type, geometry, node);
20037                 if(this.srsName) {
20038                     child.setAttribute("srsName", this.srsName);
20039                 }
20040                 return node;
20041             },
20042             "_attribute": function(obj) {
20043                 return this.createElementNSPlus(this.featurePrefix + ":" + obj.name, {
20044                     value: obj.value
20045                 });
20046             }
20047         },
20048         "wfs": {
20049             "FeatureCollection": function(features) {
20050                 /**
20051                  * This is only here because GML2 only describes abstract
20052                  * feature collections.  Typically, you would not be using
20053                  * the GML format to write wfs elements.  This just provides
20054                  * some way to write out lists of features.  GML3 defines the
20055                  * featureMembers element, so that is used by default instead.
20056                  */
20057                 var node = this.createElementNSPlus("wfs:FeatureCollection");
20058                 for(var i=0, len=features.length; i<len; ++i) {
20059                     this.writeNode("gml:featureMember", features[i], node);
20060                 }
20061                 return node;
20062             }
20063         }
20064     },
20065
20066     /**
20067      * Method: setGeometryTypes
20068      * Sets the <geometryTypes> mapping.
20069      */
20070     setGeometryTypes: function() {
20071         this.geometryTypes = {
20072             "OpenLayers.Geometry.Point": "Point",
20073             "OpenLayers.Geometry.MultiPoint": "MultiPoint",
20074             "OpenLayers.Geometry.LineString": "LineString",
20075             "OpenLayers.Geometry.MultiLineString": "MultiLineString",
20076             "OpenLayers.Geometry.Polygon": "Polygon",
20077             "OpenLayers.Geometry.MultiPolygon": "MultiPolygon",
20078             "OpenLayers.Geometry.Collection": "GeometryCollection"
20079         };
20080     },
20081
20082     CLASS_NAME: "OpenLayers.Format.GML.Base"
20083
20084 });
20085 /* ======================================================================
20086     OpenLayers/Format/GML/v2.js
20087    ====================================================================== */
20088
20089 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
20090  * full list of contributors). Published under the 2-clause BSD license.
20091  * See license.txt in the OpenLayers distribution or repository for the
20092  * full text of the license. */
20093
20094 /**
20095  * @requires OpenLayers/Format/GML/Base.js
20096  */
20097
20098 /**
20099  * Class: OpenLayers.Format.GML.v2
20100  * Parses GML version 2.
20101  *
20102  * Inherits from:
20103  *  - <OpenLayers.Format.GML.Base>
20104  */
20105 OpenLayers.Format.GML.v2 = OpenLayers.Class(OpenLayers.Format.GML.Base, {
20106     
20107     /**
20108      * Property: schemaLocation
20109      * {String} Schema location for a particular minor version.
20110      */
20111     schemaLocation: "http://www.opengis.net/gml http://schemas.opengis.net/gml/2.1.2/feature.xsd",
20112
20113     /**
20114      * Constructor: OpenLayers.Format.GML.v2
20115      * Create a parser for GML v2.
20116      *
20117      * Parameters:
20118      * options - {Object} An optional object whose properties will be set on
20119      *     this instance.
20120      *
20121      * Valid options properties:
20122      * featureType - {String} Local (without prefix) feature typeName (required).
20123      * featureNS - {String} Feature namespace (required).
20124      * geometryName - {String} Geometry element name.
20125      */
20126     initialize: function(options) {
20127         OpenLayers.Format.GML.Base.prototype.initialize.apply(this, [options]);
20128     },
20129
20130     /**
20131      * Property: readers
20132      * Contains public functions, grouped by namespace prefix, that will
20133      *     be applied when a namespaced node is found matching the function
20134      *     name.  The function will be applied in the scope of this parser
20135      *     with two arguments: the node being read and a context object passed
20136      *     from the parent.
20137      */
20138     readers: {
20139         "gml": OpenLayers.Util.applyDefaults({
20140             "outerBoundaryIs": function(node, container) {
20141                 var obj = {};
20142                 this.readChildNodes(node, obj);
20143                 container.outer = obj.components[0];
20144             },
20145             "innerBoundaryIs": function(node, container) {
20146                 var obj = {};
20147                 this.readChildNodes(node, obj);
20148                 container.inner.push(obj.components[0]);
20149             },
20150             "Box": function(node, container) {
20151                 var obj = {};
20152                 this.readChildNodes(node, obj);
20153                 if(!container.components) {
20154                     container.components = [];
20155                 }
20156                 var min = obj.points[0];
20157                 var max = obj.points[1];
20158                 container.components.push(
20159                     new OpenLayers.Bounds(min.x, min.y, max.x, max.y)
20160                 );
20161             }
20162         }, OpenLayers.Format.GML.Base.prototype.readers["gml"]),
20163         "feature": OpenLayers.Format.GML.Base.prototype.readers["feature"],
20164         "wfs": OpenLayers.Format.GML.Base.prototype.readers["wfs"]
20165     },
20166
20167     /**
20168      * Method: write
20169      *
20170      * Parameters:
20171      * features - {Array(<OpenLayers.Feature.Vector>) | OpenLayers.Feature.Vector}
20172      *     An array of features or a single feature.
20173      *
20174      * Returns:
20175      * {String} Given an array of features, a doc with a gml:featureMembers
20176      *     element will be returned.  Given a single feature, a doc with a
20177      *     gml:featureMember element will be returned.
20178      */
20179     write: function(features) {
20180         var name;
20181         if(OpenLayers.Util.isArray(features)) {
20182             // GML2 only has abstract feature collections
20183             // wfs provides a feature collection from a well-known schema
20184             name = "wfs:FeatureCollection";
20185         } else {
20186             name = "gml:featureMember";
20187         }
20188         var root = this.writeNode(name, features);
20189         this.setAttributeNS(
20190             root, this.namespaces["xsi"],
20191             "xsi:schemaLocation", this.schemaLocation
20192         );
20193
20194         return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
20195     },
20196
20197     /**
20198      * Property: writers
20199      * As a compliment to the readers property, this structure contains public
20200      *     writing functions grouped by namespace alias and named like the
20201      *     node names they produce.
20202      */
20203     writers: {
20204         "gml": OpenLayers.Util.applyDefaults({
20205             "Point": function(geometry) {
20206                 var node = this.createElementNSPlus("gml:Point");
20207                 this.writeNode("coordinates", [geometry], node);
20208                 return node;
20209             },
20210             "coordinates": function(points) {
20211                 var numPoints = points.length;
20212                 var parts = new Array(numPoints);
20213                 var point;
20214                 for(var i=0; i<numPoints; ++i) {
20215                     point = points[i];
20216                     if(this.xy) {
20217                         parts[i] = point.x + "," + point.y;
20218                     } else {
20219                         parts[i] = point.y + "," + point.x;
20220                     }
20221                     if(point.z != undefined) { // allow null or undefined
20222                         parts[i] += "," + point.z;
20223                     }
20224                 }
20225                 return this.createElementNSPlus("gml:coordinates", {
20226                     attributes: {
20227                         decimal: ".", cs: ",", ts: " "
20228                     },
20229                     value: (numPoints == 1) ? parts[0] : parts.join(" ")
20230                 });
20231             },
20232             "LineString": function(geometry) {
20233                 var node = this.createElementNSPlus("gml:LineString");
20234                 this.writeNode("coordinates", geometry.components, node);
20235                 return node;
20236             },
20237             "Polygon": function(geometry) {
20238                 var node = this.createElementNSPlus("gml:Polygon");
20239                 this.writeNode("outerBoundaryIs", geometry.components[0], node);
20240                 for(var i=1; i<geometry.components.length; ++i) {
20241                     this.writeNode(
20242                         "innerBoundaryIs", geometry.components[i], node
20243                     );
20244                 }
20245                 return node;
20246             },
20247             "outerBoundaryIs": function(ring) {
20248                 var node = this.createElementNSPlus("gml:outerBoundaryIs");
20249                 this.writeNode("LinearRing", ring, node);
20250                 return node;
20251             },
20252             "innerBoundaryIs": function(ring) {
20253                 var node = this.createElementNSPlus("gml:innerBoundaryIs");
20254                 this.writeNode("LinearRing", ring, node);
20255                 return node;
20256             },
20257             "LinearRing": function(ring) {
20258                 var node = this.createElementNSPlus("gml:LinearRing");
20259                 this.writeNode("coordinates", ring.components, node);
20260                 return node;
20261             },
20262             "Box": function(bounds) {
20263                 var node = this.createElementNSPlus("gml:Box");
20264                 this.writeNode("coordinates", [
20265                     {x: bounds.left, y: bounds.bottom},
20266                     {x: bounds.right, y: bounds.top}
20267                 ], node);
20268                 // srsName attribute is optional for gml:Box
20269                 if(this.srsName) {
20270                     node.setAttribute("srsName", this.srsName);
20271                 }
20272                 return node;
20273             }
20274         }, OpenLayers.Format.GML.Base.prototype.writers["gml"]),
20275         "feature": OpenLayers.Format.GML.Base.prototype.writers["feature"],
20276         "wfs": OpenLayers.Format.GML.Base.prototype.writers["wfs"]
20277     },
20278     
20279     CLASS_NAME: "OpenLayers.Format.GML.v2" 
20280
20281 });
20282 /* ======================================================================
20283     OpenLayers/Format/OGCExceptionReport.js
20284    ====================================================================== */
20285
20286 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
20287  * full list of contributors). Published under the 2-clause BSD license.
20288  * See license.txt in the OpenLayers distribution or repository for the
20289  * full text of the license. */
20290
20291 /**
20292  * @requires OpenLayers/Format/XML.js
20293  */
20294
20295 /**
20296  * Class: OpenLayers.Format.OGCExceptionReport
20297  * Class to read exception reports for various OGC services and versions.
20298  *
20299  * Inherits from:
20300  *  - <OpenLayers.Format.XML>
20301  */
20302 OpenLayers.Format.OGCExceptionReport = OpenLayers.Class(OpenLayers.Format.XML, {
20303
20304     /**
20305      * Property: namespaces
20306      * {Object} Mapping of namespace aliases to namespace URIs.
20307      */
20308     namespaces: {
20309         ogc: "http://www.opengis.net/ogc"
20310     },
20311
20312     /**
20313      * Property: regExes
20314      * Compiled regular expressions for manipulating strings.
20315      */
20316     regExes: {
20317         trimSpace: (/^\s*|\s*$/g),
20318         removeSpace: (/\s*/g),
20319         splitSpace: (/\s+/),
20320         trimComma: (/\s*,\s*/g)
20321     },
20322
20323     /**
20324      * Property: defaultPrefix
20325      */
20326     defaultPrefix: "ogc",
20327
20328     /**
20329      * Constructor: OpenLayers.Format.OGCExceptionReport
20330      * Create a new parser for OGC exception reports.
20331      *
20332      * Parameters:
20333      * options - {Object} An optional object whose properties will be set on
20334      *     this instance.
20335      */
20336
20337     /**
20338      * APIMethod: read
20339      * Read OGC exception report data from a string, and return an object with
20340      * information about the exceptions.
20341      *
20342      * Parameters:
20343      * data - {String} or {DOMElement} data to read/parse.
20344      *
20345      * Returns:
20346      * {Object} Information about the exceptions that occurred.
20347      */
20348     read: function(data) {
20349         var result;
20350         if(typeof data == "string") {
20351             data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
20352         }
20353         var root = data.documentElement;
20354         var exceptionInfo = {exceptionReport: null}; 
20355         if (root) {
20356             this.readChildNodes(data, exceptionInfo);
20357             if (exceptionInfo.exceptionReport === null) {
20358                 // fall-back to OWSCommon since this is a common output format for exceptions
20359                 // we cannot easily use the ows readers directly since they differ for 1.0 and 1.1
20360                 exceptionInfo = new OpenLayers.Format.OWSCommon().read(data);
20361             }
20362         }
20363         return exceptionInfo;
20364     },
20365
20366     /**
20367      * Property: readers
20368      * Contains public functions, grouped by namespace prefix, that will
20369      *     be applied when a namespaced node is found matching the function
20370      *     name.  The function will be applied in the scope of this parser
20371      *     with two arguments: the node being read and a context object passed
20372      *     from the parent.
20373      */
20374     readers: {
20375         "ogc": {
20376             "ServiceExceptionReport": function(node, obj) {
20377                 obj.exceptionReport = {exceptions: []};
20378                 this.readChildNodes(node, obj.exceptionReport);
20379             },
20380             "ServiceException": function(node, exceptionReport) {
20381                 var exception = {
20382                     code: node.getAttribute("code"),
20383                     locator: node.getAttribute("locator"),
20384                     text: this.getChildValue(node)
20385                 };
20386                 exceptionReport.exceptions.push(exception);
20387             }
20388         }
20389     },
20390     
20391     CLASS_NAME: "OpenLayers.Format.OGCExceptionReport"
20392     
20393 });
20394 /* ======================================================================
20395     OpenLayers/Format/XML/VersionedOGC.js
20396    ====================================================================== */
20397
20398 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
20399  * full list of contributors). Published under the 2-clause BSD license.
20400  * See license.txt in the OpenLayers distribution or repository for the
20401  * full text of the license. */
20402
20403 /**
20404  * @requires OpenLayers/Format/XML.js
20405  * @requires OpenLayers/Format/OGCExceptionReport.js
20406  */
20407
20408 /**
20409  * Class: OpenLayers.Format.XML.VersionedOGC
20410  * Base class for versioned formats, i.e. a format which supports multiple
20411  * versions.
20412  *
20413  * To enable checking if parsing succeeded, you will need to define a property
20414  * called errorProperty on the parser you want to check. The parser will then
20415  * check the returned object to see if that property is present. If it is, it
20416  * assumes the parsing was successful. If it is not present (or is null), it will
20417  * pass the document through an OGCExceptionReport parser.
20418  * 
20419  * If errorProperty is undefined for the parser, this error checking mechanism
20420  * will be disabled.
20421  *
20422  *
20423  * 
20424  * Inherits from:
20425  *  - <OpenLayers.Format.XML>
20426  */
20427 OpenLayers.Format.XML.VersionedOGC = OpenLayers.Class(OpenLayers.Format.XML, {
20428     
20429     /**
20430      * APIProperty: defaultVersion
20431      * {String} Version number to assume if none found.
20432      */
20433     defaultVersion: null,
20434     
20435     /**
20436      * APIProperty: version
20437      * {String} Specify a version string if one is known.
20438      */
20439     version: null,
20440
20441     /**
20442      * APIProperty: profile
20443      * {String} If provided, use a custom profile.
20444      */
20445     profile: null,
20446
20447     /**
20448      * APIProperty: allowFallback
20449      * {Boolean} If a profiled parser cannot be found for the returned version,
20450      * use a non-profiled parser as the fallback. Application code using this
20451      * should take into account that the return object structure might be
20452      * missing the specifics of the profile. Defaults to false.
20453      */
20454     allowFallback: false,
20455
20456     /**
20457      * Property: name
20458      * {String} The name of this parser, this is the part of the CLASS_NAME
20459      * except for "OpenLayers.Format."
20460      */
20461     name: null,
20462
20463     /**
20464      * APIProperty: stringifyOutput
20465      * {Boolean} If true, write will return a string otherwise a DOMElement.
20466      * Default is false.
20467      */
20468     stringifyOutput: false,
20469
20470     /**
20471      * Property: parser
20472      * {Object} Instance of the versioned parser.  Cached for multiple read and
20473      *     write calls of the same version.
20474      */
20475     parser: null,
20476
20477     /**
20478      * Constructor: OpenLayers.Format.XML.VersionedOGC.
20479      * Constructor.
20480      *
20481      * Parameters:
20482      * options - {Object} Optional object whose properties will be set on
20483      *     the object.
20484      */
20485     initialize: function(options) {
20486         OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
20487         var className = this.CLASS_NAME;
20488         this.name = className.substring(className.lastIndexOf(".")+1);
20489     },
20490
20491     /**
20492      * Method: getVersion
20493      * Returns the version to use. Subclasses can override this function
20494      * if a different version detection is needed.
20495      *
20496      * Parameters:
20497      * root - {DOMElement}
20498      * options - {Object} Optional configuration object.
20499      *
20500      * Returns:
20501      * {String} The version to use.
20502      */
20503     getVersion: function(root, options) {
20504         var version;
20505         // read
20506         if (root) {
20507             version = this.version;
20508             if(!version) {
20509                 version = root.getAttribute("version");
20510                 if(!version) {
20511                     version = this.defaultVersion;
20512                 }
20513             }
20514         } else { // write
20515             version = (options && options.version) || 
20516                 this.version || this.defaultVersion;
20517         }
20518         return version;
20519     },
20520
20521     /**
20522      * Method: getParser
20523      * Get an instance of the cached parser if available, otherwise create one.
20524      *
20525      * Parameters:
20526      * version - {String}
20527      *
20528      * Returns:
20529      * {<OpenLayers.Format>}
20530      */
20531     getParser: function(version) {
20532         version = version || this.defaultVersion;
20533         var profile = this.profile ? "_" + this.profile : "";
20534         if(!this.parser || this.parser.VERSION != version) {
20535             var format = OpenLayers.Format[this.name][
20536                 "v" + version.replace(/\./g, "_") + profile
20537             ];
20538             if(!format) {
20539                 if (profile !== "" && this.allowFallback) {
20540                     // fallback to the non-profiled version of the parser
20541                     profile = "";
20542                     format = OpenLayers.Format[this.name][
20543                         "v" + version.replace(/\./g, "_")
20544                     ];
20545                 }
20546                 if (!format) {
20547                     throw "Can't find a " + this.name + " parser for version " +
20548                           version + profile;
20549                 }
20550             }
20551             this.parser = new format(this.options);
20552         }
20553         return this.parser;
20554     },
20555
20556     /**
20557      * APIMethod: write
20558      * Write a document.
20559      *
20560      * Parameters:
20561      * obj - {Object} An object representing the document.
20562      * options - {Object} Optional configuration object.
20563      *
20564      * Returns:
20565      * {String} The document as a string
20566      */
20567     write: function(obj, options) {
20568         var version = this.getVersion(null, options);
20569         this.parser = this.getParser(version);
20570         var root = this.parser.write(obj, options);
20571         if (this.stringifyOutput === false) {
20572             return root;
20573         } else {
20574             return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
20575         }
20576     },
20577
20578     /**
20579      * APIMethod: read
20580      * Read a doc and return an object representing the document.
20581      *
20582      * Parameters:
20583      * data - {String | DOMElement} Data to read.
20584      * options - {Object} Options for the reader.
20585      *
20586      * Returns:
20587      * {Object} An object representing the document.
20588      */
20589     read: function(data, options) {
20590         if(typeof data == "string") {
20591             data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
20592         }
20593         var root = data.documentElement;
20594         var version = this.getVersion(root);
20595         this.parser = this.getParser(version);          // Select the parser
20596         var obj = this.parser.read(data, options);      // Parse the data
20597
20598         var errorProperty = this.parser.errorProperty || null;
20599         if (errorProperty !== null && obj[errorProperty] === undefined) {
20600             // an error must have happened, so parse it and report back
20601             var format = new OpenLayers.Format.OGCExceptionReport();
20602             obj.error = format.read(data);
20603         }
20604         obj.version = version;
20605         obj.requestType = this.name;
20606         return obj;
20607     },
20608
20609     CLASS_NAME: "OpenLayers.Format.XML.VersionedOGC"
20610 });
20611 /* ======================================================================
20612     OpenLayers/Filter/Logical.js
20613    ====================================================================== */
20614
20615 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
20616  * full list of contributors). Published under the 2-clause BSD license.
20617  * See license.txt in the OpenLayers distribution or repository for the
20618  * full text of the license. */
20619
20620
20621 /**
20622  * @requires OpenLayers/Filter.js
20623  */
20624
20625 /**
20626  * Class: OpenLayers.Filter.Logical
20627  * This class represents ogc:And, ogc:Or and ogc:Not rules.
20628  * 
20629  * Inherits from:
20630  * - <OpenLayers.Filter>
20631  */
20632 OpenLayers.Filter.Logical = OpenLayers.Class(OpenLayers.Filter, {
20633
20634     /**
20635      * APIProperty: filters
20636      * {Array(<OpenLayers.Filter>)} Child filters for this filter.
20637      */
20638     filters: null, 
20639      
20640     /**
20641      * APIProperty: type
20642      * {String} type of logical operator. Available types are:
20643      * - OpenLayers.Filter.Logical.AND = "&&";
20644      * - OpenLayers.Filter.Logical.OR  = "||";
20645      * - OpenLayers.Filter.Logical.NOT = "!";
20646      */
20647     type: null,
20648
20649     /** 
20650      * Constructor: OpenLayers.Filter.Logical
20651      * Creates a logical filter (And, Or, Not).
20652      *
20653      * Parameters:
20654      * options - {Object} An optional object with properties to set on the
20655      *     filter.
20656      * 
20657      * Returns:
20658      * {<OpenLayers.Filter.Logical>}
20659      */
20660     initialize: function(options) {
20661         this.filters = [];
20662         OpenLayers.Filter.prototype.initialize.apply(this, [options]);
20663     },
20664     
20665     /** 
20666      * APIMethod: destroy
20667      * Remove reference to child filters.
20668      */
20669     destroy: function() {
20670         this.filters = null;
20671         OpenLayers.Filter.prototype.destroy.apply(this);
20672     },
20673
20674     /**
20675      * APIMethod: evaluate
20676      * Evaluates this filter in a specific context.
20677      * 
20678      * Parameters:
20679      * context - {Object} Context to use in evaluating the filter.  A vector
20680      *     feature may also be provided to evaluate feature attributes in 
20681      *     comparison filters or geometries in spatial filters.
20682      * 
20683      * Returns:
20684      * {Boolean} The filter applies.
20685      */
20686     evaluate: function(context) {
20687         var i, len;
20688         switch(this.type) {
20689             case OpenLayers.Filter.Logical.AND:
20690                 for (i=0, len=this.filters.length; i<len; i++) {
20691                     if (this.filters[i].evaluate(context) == false) {
20692                         return false;
20693                     }
20694                 }
20695                 return true;
20696                 
20697             case OpenLayers.Filter.Logical.OR:
20698                 for (i=0, len=this.filters.length; i<len; i++) {
20699                     if (this.filters[i].evaluate(context) == true) {
20700                         return true;
20701                     }
20702                 }
20703                 return false;
20704             
20705             case OpenLayers.Filter.Logical.NOT:
20706                 return (!this.filters[0].evaluate(context));
20707         }
20708         return undefined;
20709     },
20710     
20711     /**
20712      * APIMethod: clone
20713      * Clones this filter.
20714      * 
20715      * Returns:
20716      * {<OpenLayers.Filter.Logical>} Clone of this filter.
20717      */
20718     clone: function() {
20719         var filters = [];        
20720         for(var i=0, len=this.filters.length; i<len; ++i) {
20721             filters.push(this.filters[i].clone());
20722         }
20723         return new OpenLayers.Filter.Logical({
20724             type: this.type,
20725             filters: filters
20726         });
20727     },
20728     
20729     CLASS_NAME: "OpenLayers.Filter.Logical"
20730 });
20731
20732
20733 OpenLayers.Filter.Logical.AND = "&&";
20734 OpenLayers.Filter.Logical.OR  = "||";
20735 OpenLayers.Filter.Logical.NOT = "!";
20736 /* ======================================================================
20737     OpenLayers/Filter/Comparison.js
20738    ====================================================================== */
20739
20740 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
20741  * full list of contributors). Published under the 2-clause BSD license.
20742  * See license.txt in the OpenLayers distribution or repository for the
20743  * full text of the license. */
20744
20745 /**
20746  * @requires OpenLayers/Filter.js
20747  */
20748
20749 /**
20750  * Class: OpenLayers.Filter.Comparison
20751  * This class represents a comparison filter.
20752  * 
20753  * Inherits from:
20754  * - <OpenLayers.Filter>
20755  */
20756 OpenLayers.Filter.Comparison = OpenLayers.Class(OpenLayers.Filter, {
20757
20758     /**
20759      * APIProperty: type
20760      * {String} type: type of the comparison. This is one of
20761      * - OpenLayers.Filter.Comparison.EQUAL_TO                 = "==";
20762      * - OpenLayers.Filter.Comparison.NOT_EQUAL_TO             = "!=";
20763      * - OpenLayers.Filter.Comparison.LESS_THAN                = "<";
20764      * - OpenLayers.Filter.Comparison.GREATER_THAN             = ">";
20765      * - OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO    = "<=";
20766      * - OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO = ">=";
20767      * - OpenLayers.Filter.Comparison.BETWEEN                  = "..";
20768      * - OpenLayers.Filter.Comparison.LIKE                     = "~";
20769      * - OpenLayers.Filter.Comparison.IS_NULL                  = "NULL";
20770      */
20771     type: null,
20772     
20773     /**
20774      * APIProperty: property
20775      * {String}
20776      * name of the context property to compare
20777      */
20778     property: null,
20779     
20780     /**
20781      * APIProperty: value
20782      * {Number} or {String}
20783      * comparison value for binary comparisons. In the case of a String, this
20784      * can be a combination of text and propertyNames in the form
20785      * "literal ${propertyName}"
20786      */
20787     value: null,
20788     
20789     /**
20790      * Property: matchCase
20791      * {Boolean} Force case sensitive searches for EQUAL_TO and NOT_EQUAL_TO
20792      *     comparisons.  The Filter Encoding 1.1 specification added a matchCase
20793      *     attribute to ogc:PropertyIsEqualTo and ogc:PropertyIsNotEqualTo
20794      *     elements.  This property will be serialized with those elements only
20795      *     if using the v1.1.0 filter format. However, when evaluating filters
20796      *     here, the matchCase property will always be respected (for EQUAL_TO
20797      *     and NOT_EQUAL_TO).  Default is true. 
20798      */
20799     matchCase: true,
20800     
20801     /**
20802      * APIProperty: lowerBoundary
20803      * {Number} or {String}
20804      * lower boundary for between comparisons. In the case of a String, this
20805      * can be a combination of text and propertyNames in the form
20806      * "literal ${propertyName}"
20807      */
20808     lowerBoundary: null,
20809     
20810     /**
20811      * APIProperty: upperBoundary
20812      * {Number} or {String}
20813      * upper boundary for between comparisons. In the case of a String, this
20814      * can be a combination of text and propertyNames in the form
20815      * "literal ${propertyName}"
20816      */
20817     upperBoundary: null,
20818
20819     /** 
20820      * Constructor: OpenLayers.Filter.Comparison
20821      * Creates a comparison rule.
20822      *
20823      * Parameters:
20824      * options - {Object} An optional object with properties to set on the
20825      *           rule
20826      * 
20827      * Returns:
20828      * {<OpenLayers.Filter.Comparison>}
20829      */
20830     initialize: function(options) {
20831         OpenLayers.Filter.prototype.initialize.apply(this, [options]);
20832         // since matchCase on PropertyIsLike is not schema compliant, we only
20833         // want to use this if explicitly asked for
20834         if (this.type === OpenLayers.Filter.Comparison.LIKE 
20835             && options.matchCase === undefined) {
20836                 this.matchCase = null;
20837         }
20838     },
20839
20840     /**
20841      * APIMethod: evaluate
20842      * Evaluates this filter in a specific context.
20843      * 
20844      * Parameters:
20845      * context - {Object} Context to use in evaluating the filter.  If a vector
20846      *     feature is provided, the feature.attributes will be used as context.
20847      * 
20848      * Returns:
20849      * {Boolean} The filter applies.
20850      */
20851     evaluate: function(context) {
20852         if (context instanceof OpenLayers.Feature.Vector) {
20853             context = context.attributes;
20854         }
20855         var result = false;
20856         var got = context[this.property];
20857         if (got === undefined) {
20858             return false;
20859         }
20860         var exp;
20861         switch(this.type) {
20862             case OpenLayers.Filter.Comparison.EQUAL_TO:
20863                 exp = this.value;
20864                 if(!this.matchCase &&
20865                    typeof got == "string" && typeof exp == "string") {
20866                     result = (got.toUpperCase() == exp.toUpperCase());
20867                 } else {
20868                     result = (got == exp);
20869                 }
20870                 break;
20871             case OpenLayers.Filter.Comparison.NOT_EQUAL_TO:
20872                 exp = this.value;
20873                 if(!this.matchCase &&
20874                    typeof got == "string" && typeof exp == "string") {
20875                     result = (got.toUpperCase() != exp.toUpperCase());
20876                 } else {
20877                     result = (got != exp);
20878                 }
20879                 break;
20880             case OpenLayers.Filter.Comparison.LESS_THAN:
20881                 result = got < this.value;
20882                 break;
20883             case OpenLayers.Filter.Comparison.GREATER_THAN:
20884                 result = got > this.value;
20885                 break;
20886             case OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO:
20887                 result = got <= this.value;
20888                 break;
20889             case OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO:
20890                 result = got >= this.value;
20891                 break;
20892             case OpenLayers.Filter.Comparison.BETWEEN:
20893                 result = (got >= this.lowerBoundary) &&
20894                     (got <= this.upperBoundary);
20895                 break;
20896             case OpenLayers.Filter.Comparison.LIKE:
20897                 var regexp = new RegExp(this.value, "gi");
20898                 result = regexp.test(got);
20899                 break;
20900             case OpenLayers.Filter.Comparison.IS_NULL:
20901                 result = (got === null);
20902                 break;
20903         }
20904         return result;
20905     },
20906     
20907     /**
20908      * APIMethod: value2regex
20909      * Converts the value of this rule into a regular expression string,
20910      * according to the wildcard characters specified. This method has to
20911      * be called after instantiation of this class, if the value is not a
20912      * regular expression already.
20913      * 
20914      * Parameters:
20915      * wildCard   - {Char} wildcard character in the above value, default
20916      *              is "*"
20917      * singleChar - {Char} single-character wildcard in the above value
20918      *              default is "."
20919      * escapeChar - {Char} escape character in the above value, default is
20920      *              "!"
20921      * 
20922      * Returns:
20923      * {String} regular expression string
20924      */
20925     value2regex: function(wildCard, singleChar, escapeChar) {
20926         if (wildCard == ".") {
20927             throw new Error("'.' is an unsupported wildCard character for " +
20928                             "OpenLayers.Filter.Comparison");
20929         }
20930         
20931
20932         // set UMN MapServer defaults for unspecified parameters
20933         wildCard = wildCard ? wildCard : "*";
20934         singleChar = singleChar ? singleChar : ".";
20935         escapeChar = escapeChar ? escapeChar : "!";
20936         
20937         this.value = this.value.replace(
20938                 new RegExp("\\"+escapeChar+"(.|$)", "g"), "\\$1");
20939         this.value = this.value.replace(
20940                 new RegExp("\\"+singleChar, "g"), ".");
20941         this.value = this.value.replace(
20942                 new RegExp("\\"+wildCard, "g"), ".*");
20943         this.value = this.value.replace(
20944                 new RegExp("\\\\.\\*", "g"), "\\"+wildCard);
20945         this.value = this.value.replace(
20946                 new RegExp("\\\\\\.", "g"), "\\"+singleChar);
20947         
20948         return this.value;
20949     },
20950     
20951     /**
20952      * Method: regex2value
20953      * Convert the value of this rule from a regular expression string into an
20954      *     ogc literal string using a wildCard of *, a singleChar of ., and an
20955      *     escape of !.  Leaves the <value> property unmodified.
20956      * 
20957      * Returns:
20958      * {String} A string value.
20959      */
20960     regex2value: function() {
20961         
20962         var value = this.value;
20963         
20964         // replace ! with !!
20965         value = value.replace(/!/g, "!!");
20966
20967         // replace \. with !. (watching out for \\.)
20968         value = value.replace(/(\\)?\\\./g, function($0, $1) {
20969             return $1 ? $0 : "!.";
20970         });
20971         
20972         // replace \* with #* (watching out for \\*)
20973         value = value.replace(/(\\)?\\\*/g, function($0, $1) {
20974             return $1 ? $0 : "!*";
20975         });
20976         
20977         // replace \\ with \
20978         value = value.replace(/\\\\/g, "\\");
20979
20980         // convert .* to * (the sequence #.* is not allowed)
20981         value = value.replace(/\.\*/g, "*");
20982         
20983         return value;
20984     },
20985     
20986     /**
20987      * APIMethod: clone
20988      * Clones this filter.
20989      * 
20990      * Returns:
20991      * {<OpenLayers.Filter.Comparison>} Clone of this filter.
20992      */
20993     clone: function() {
20994         return OpenLayers.Util.extend(new OpenLayers.Filter.Comparison(), this);
20995     },
20996     
20997     CLASS_NAME: "OpenLayers.Filter.Comparison"
20998 });
20999
21000
21001 OpenLayers.Filter.Comparison.EQUAL_TO                 = "==";
21002 OpenLayers.Filter.Comparison.NOT_EQUAL_TO             = "!=";
21003 OpenLayers.Filter.Comparison.LESS_THAN                = "<";
21004 OpenLayers.Filter.Comparison.GREATER_THAN             = ">";
21005 OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO    = "<=";
21006 OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO = ">=";
21007 OpenLayers.Filter.Comparison.BETWEEN                  = "..";
21008 OpenLayers.Filter.Comparison.LIKE                     = "~";
21009 OpenLayers.Filter.Comparison.IS_NULL                  = "NULL";
21010 /* ======================================================================
21011     OpenLayers/Format/Filter.js
21012    ====================================================================== */
21013
21014 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
21015  * full list of contributors). Published under the 2-clause BSD license.
21016  * See license.txt in the OpenLayers distribution or repository for the
21017  * full text of the license. */
21018
21019 /**
21020  * @requires OpenLayers/Format/XML/VersionedOGC.js
21021  * @requires OpenLayers/Filter/FeatureId.js
21022  * @requires OpenLayers/Filter/Logical.js
21023  * @requires OpenLayers/Filter/Comparison.js
21024  */
21025
21026 /**
21027  * Class: OpenLayers.Format.Filter
21028  * Read/Write ogc:Filter. Create a new instance with the <OpenLayers.Format.Filter>
21029  *     constructor.
21030  * 
21031  * Inherits from:
21032  *  - <OpenLayers.Format.XML.VersionedOGC>
21033  */
21034 OpenLayers.Format.Filter = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
21035     
21036     /**
21037      * APIProperty: defaultVersion
21038      * {String} Version number to assume if none found.  Default is "1.0.0".
21039      */
21040     defaultVersion: "1.0.0",
21041     
21042     /**
21043      * APIMethod: write
21044      * Write an ogc:Filter given a filter object.
21045      *
21046      * Parameters:
21047      * filter - {<OpenLayers.Filter>} An filter.
21048      * options - {Object} Optional configuration object.
21049      *
21050      * Returns:
21051      * {Elment} An ogc:Filter element node.
21052      */
21053     
21054     /**
21055      * APIMethod: read
21056      * Read and Filter doc and return an object representing the Filter.
21057      *
21058      * Parameters:
21059      * data - {String | DOMElement} Data to read.
21060      *
21061      * Returns:
21062      * {<OpenLayers.Filter>} A filter object.
21063      */
21064
21065     CLASS_NAME: "OpenLayers.Format.Filter" 
21066 });
21067 /* ======================================================================
21068     OpenLayers/Filter/Function.js
21069    ====================================================================== */
21070
21071 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
21072  * full list of contributors). Published under the 2-clause BSD license.
21073  * See license.txt in the OpenLayers distribution or repository for the
21074  * full text of the license. */
21075
21076 /**
21077  * @requires OpenLayers/Filter.js
21078  */
21079
21080 /**
21081  * Class: OpenLayers.Filter.Function
21082  * This class represents a filter function.
21083  * We are using this class for creation of complex 
21084  * filters that can contain filter functions as values.
21085  * Nesting function as other functions parameter is supported.
21086  * 
21087  * Inherits from:
21088  * - <OpenLayers.Filter>
21089  */
21090 OpenLayers.Filter.Function = OpenLayers.Class(OpenLayers.Filter, {
21091
21092     /**
21093      * APIProperty: name
21094      * {String} Name of the function.
21095      */
21096     name: null,
21097     
21098     /**
21099      * APIProperty: params
21100      * {Array(<OpenLayers.Filter.Function> || String || Number)} Function parameters
21101      * For now support only other Functions, String or Number
21102      */
21103     params: null,  
21104     
21105     /** 
21106      * Constructor: OpenLayers.Filter.Function
21107      * Creates a filter function.
21108      *
21109      * Parameters:
21110      * options - {Object} An optional object with properties to set on the
21111      *     function.
21112      * 
21113      * Returns:
21114      * {<OpenLayers.Filter.Function>}
21115      */
21116
21117     CLASS_NAME: "OpenLayers.Filter.Function"
21118 });
21119
21120 /* ======================================================================
21121     OpenLayers/BaseTypes/Date.js
21122    ====================================================================== */
21123
21124 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
21125  * full list of contributors). Published under the 2-clause BSD license.
21126  * See license.txt in the OpenLayers distribution or repository for the
21127  * full text of the license. */
21128
21129 /**
21130  * @requires OpenLayers/SingleFile.js
21131  */
21132
21133 /**
21134  * Namespace: OpenLayers.Date
21135  * Contains implementations of Date.parse and date.toISOString that match the
21136  *     ECMAScript 5 specification for parsing RFC 3339 dates.
21137  *     http://tools.ietf.org/html/rfc3339
21138  */
21139 OpenLayers.Date = {
21140
21141     /** 
21142      * APIProperty: dateRegEx
21143      * The regex to be used for validating dates. You can provide your own
21144      * regex for instance for adding support for years before BC. Default
21145      * value is: /^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:(?:T(\d{1,2}):(\d{2}):(\d{2}(?:\.\d+)?)(Z|(?:[+-]\d{1,2}(?::(\d{2}))?)))|Z)?$/
21146      */
21147     dateRegEx: /^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:(?:T(\d{1,2}):(\d{2}):(\d{2}(?:\.\d+)?)(Z|(?:[+-]\d{1,2}(?::(\d{2}))?)))|Z)?$/,
21148
21149     /**
21150      * APIMethod: toISOString
21151      * Generates a string representing a date.  The format of the string follows
21152      *     the profile of ISO 8601 for date and time on the Internet (see
21153      *     http://tools.ietf.org/html/rfc3339).  If the toISOString method is
21154      *     available on the Date prototype, that is used.  The toISOString
21155      *     method for Date instances is defined in ECMA-262.
21156      *
21157      * Parameters:
21158      * date - {Date} A date object.
21159      *
21160      * Returns:
21161      * {String} A string representing the date (e.g.
21162      *     "2010-08-07T16:58:23.123Z").  If the date does not have a valid time
21163      *     (i.e. isNaN(date.getTime())) this method returns the string "Invalid
21164      *     Date".  The ECMA standard says the toISOString method should throw
21165      *     RangeError in this case, but Firefox returns a string instead.  For
21166      *     best results, use isNaN(date.getTime()) to determine date validity
21167      *     before generating date strings.
21168      */
21169     toISOString: (function() {
21170         if ("toISOString" in Date.prototype) {
21171             return function(date) {
21172                 return date.toISOString();
21173             };
21174         } else {
21175             return function(date) {
21176                 var str;
21177                 if (isNaN(date.getTime())) {
21178                     // ECMA-262 says throw RangeError, Firefox returns
21179                     // "Invalid Date"
21180                     str = "Invalid Date";
21181                 } else {
21182                     str =
21183                         date.getUTCFullYear() + "-" +
21184                         OpenLayers.Number.zeroPad(date.getUTCMonth() + 1, 2) + "-" +
21185                         OpenLayers.Number.zeroPad(date.getUTCDate(), 2) + "T" +
21186                         OpenLayers.Number.zeroPad(date.getUTCHours(), 2) + ":" +
21187                         OpenLayers.Number.zeroPad(date.getUTCMinutes(), 2) + ":" +
21188                         OpenLayers.Number.zeroPad(date.getUTCSeconds(), 2) + "." +
21189                         OpenLayers.Number.zeroPad(date.getUTCMilliseconds(), 3) + "Z";
21190                 }
21191                 return str;
21192             };
21193         }
21194
21195     })(),
21196
21197     /**
21198      * APIMethod: parse
21199      * Generate a date object from a string.  The format for the string follows
21200      *     the profile of ISO 8601 for date and time on the Internet (see
21201      *     http://tools.ietf.org/html/rfc3339).  We don't call the native
21202      *     Date.parse because of inconsistency between implmentations.  In
21203      *     Chrome, calling Date.parse with a string that doesn't contain any
21204      *     indication of the timezone (e.g. "2011"), the date is interpreted
21205      *     in local time.  On Firefox, the assumption is UTC.
21206      *
21207      * Parameters:
21208      * str - {String} A string representing the date (e.g.
21209      *     "2010", "2010-08", "2010-08-07", "2010-08-07T16:58:23.123Z",
21210      *     "2010-08-07T11:58:23.123-06").
21211      *
21212      * Returns:
21213      * {Date} A date object.  If the string could not be parsed, an invalid
21214      *     date is returned (i.e. isNaN(date.getTime())).
21215      */
21216     parse: function(str) {
21217         var date;
21218         var match = str.match(this.dateRegEx);
21219         if (match && (match[1] || match[7])) { // must have at least year or time
21220             var year = parseInt(match[1], 10) || 0;
21221             var month = (parseInt(match[2], 10) - 1) || 0;
21222             var day = parseInt(match[3], 10) || 1;
21223             date = new Date(Date.UTC(year, month, day));
21224             // optional time
21225             var type = match[7];
21226             if (type) {
21227                 var hours = parseInt(match[4], 10);
21228                 var minutes = parseInt(match[5], 10);
21229                 var secFrac = parseFloat(match[6]);
21230                 var seconds = secFrac | 0;
21231                 var milliseconds = Math.round(1000 * (secFrac - seconds));
21232                 date.setUTCHours(hours, minutes, seconds, milliseconds);
21233                 // check offset
21234                 if (type !== "Z") {
21235                     var hoursOffset = parseInt(type, 10);
21236                     var minutesOffset = parseInt(match[8], 10) || 0;
21237                     var offset = -1000 * (60 * (hoursOffset * 60) + minutesOffset * 60);
21238                     date = new Date(date.getTime() + offset);
21239                 }
21240             }
21241         } else {
21242             date = new Date("invalid");
21243         }
21244         return date;
21245     }
21246 };
21247 /* ======================================================================
21248     OpenLayers/Format/Filter/v1.js
21249    ====================================================================== */
21250
21251 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
21252  * full list of contributors). Published under the 2-clause BSD license.
21253  * See license.txt in the OpenLayers distribution or repository for the
21254  * full text of the license. */
21255 /**
21256  * @requires OpenLayers/Format/Filter.js
21257  * @requires OpenLayers/Format/XML.js
21258  * @requires OpenLayers/Filter/Function.js
21259  * @requires OpenLayers/BaseTypes/Date.js
21260  */
21261
21262 /**
21263  * Class: OpenLayers.Format.Filter.v1
21264  * Superclass for Filter version 1 parsers.
21265  *
21266  * Inherits from:
21267  *  - <OpenLayers.Format.XML>
21268  */
21269 OpenLayers.Format.Filter.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
21270     
21271     /**
21272      * Property: namespaces
21273      * {Object} Mapping of namespace aliases to namespace URIs.
21274      */
21275     namespaces: {
21276         ogc: "http://www.opengis.net/ogc",
21277         gml: "http://www.opengis.net/gml",
21278         xlink: "http://www.w3.org/1999/xlink",
21279         xsi: "http://www.w3.org/2001/XMLSchema-instance"
21280     },
21281
21282     /**
21283      * Property: defaultPrefix
21284      */
21285     defaultPrefix: "ogc",
21286
21287     /**
21288      * Property: schemaLocation
21289      * {String} Schema location for a particular minor version.
21290      */
21291     schemaLocation: null,
21292     
21293     /**
21294      * Constructor: OpenLayers.Format.Filter.v1
21295      * Instances of this class are not created directly.  Use the
21296      *     <OpenLayers.Format.Filter> constructor instead.
21297      *
21298      * Parameters:
21299      * options - {Object} An optional object whose properties will be set on
21300      *     this instance.
21301      */
21302     initialize: function(options) {
21303         OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
21304     },
21305     
21306     /**
21307      * Method: read
21308      *
21309      * Parameters:
21310      * data - {DOMElement} A Filter document element.
21311      *
21312      * Returns:
21313      * {<OpenLayers.Filter>} A filter object.
21314      */
21315     read: function(data) {
21316         var obj = {};
21317         this.readers.ogc["Filter"].apply(this, [data, obj]);
21318         return obj.filter;
21319     },
21320     
21321     /**
21322      * Property: readers
21323      * Contains public functions, grouped by namespace prefix, that will
21324      *     be applied when a namespaced node is found matching the function
21325      *     name.  The function will be applied in the scope of this parser
21326      *     with two arguments: the node being read and a context object passed
21327      *     from the parent.
21328      */
21329     readers: {
21330         "ogc": {
21331             "_expression": function(node) {
21332                 // only the simplest of ogc:expression handled
21333                 // "some text and an <PropertyName>attribute</PropertyName>"}
21334                 var obj, value = "";
21335                 for(var child=node.firstChild; child; child=child.nextSibling) {
21336                     switch(child.nodeType) {
21337                         case 1:
21338                             obj = this.readNode(child);
21339                             if (obj.property) {
21340                                 value += "${" + obj.property + "}";
21341                             } else if (obj.value !== undefined) {
21342                                 value += obj.value;
21343                             }
21344                             break;
21345                         case 3: // text node
21346                         case 4: // cdata section
21347                             value += child.nodeValue;
21348                     }
21349                 }
21350                 return value;
21351             },
21352             "Filter": function(node, parent) {
21353                 // Filters correspond to subclasses of OpenLayers.Filter.
21354                 // Since they contain information we don't persist, we
21355                 // create a temporary object and then pass on the filter
21356                 // (ogc:Filter) to the parent obj.
21357                 var obj = {
21358                     fids: [],
21359                     filters: []
21360                 };
21361                 this.readChildNodes(node, obj);
21362                 if(obj.fids.length > 0) {
21363                     parent.filter = new OpenLayers.Filter.FeatureId({
21364                         fids: obj.fids
21365                     });
21366                 } else if(obj.filters.length > 0) {
21367                     parent.filter = obj.filters[0];
21368                 }
21369             },
21370             "FeatureId": function(node, obj) {
21371                 var fid = node.getAttribute("fid");
21372                 if(fid) {
21373                     obj.fids.push(fid);
21374                 }
21375             },
21376             "And": function(node, obj) {
21377                 var filter = new OpenLayers.Filter.Logical({
21378                     type: OpenLayers.Filter.Logical.AND
21379                 });
21380                 this.readChildNodes(node, filter);
21381                 obj.filters.push(filter);
21382             },
21383             "Or": function(node, obj) {
21384                 var filter = new OpenLayers.Filter.Logical({
21385                     type: OpenLayers.Filter.Logical.OR
21386                 });
21387                 this.readChildNodes(node, filter);
21388                 obj.filters.push(filter);
21389             },
21390             "Not": function(node, obj) {
21391                 var filter = new OpenLayers.Filter.Logical({
21392                     type: OpenLayers.Filter.Logical.NOT
21393                 });
21394                 this.readChildNodes(node, filter);
21395                 obj.filters.push(filter);
21396             },
21397             "PropertyIsLessThan": function(node, obj) {
21398                 var filter = new OpenLayers.Filter.Comparison({
21399                     type: OpenLayers.Filter.Comparison.LESS_THAN
21400                 });
21401                 this.readChildNodes(node, filter);
21402                 obj.filters.push(filter);
21403             },
21404             "PropertyIsGreaterThan": function(node, obj) {
21405                 var filter = new OpenLayers.Filter.Comparison({
21406                     type: OpenLayers.Filter.Comparison.GREATER_THAN
21407                 });
21408                 this.readChildNodes(node, filter);
21409                 obj.filters.push(filter);
21410             },
21411             "PropertyIsLessThanOrEqualTo": function(node, obj) {
21412                 var filter = new OpenLayers.Filter.Comparison({
21413                     type: OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO
21414                 });
21415                 this.readChildNodes(node, filter);
21416                 obj.filters.push(filter);
21417             },
21418             "PropertyIsGreaterThanOrEqualTo": function(node, obj) {
21419                 var filter = new OpenLayers.Filter.Comparison({
21420                     type: OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO
21421                 });
21422                 this.readChildNodes(node, filter);
21423                 obj.filters.push(filter);
21424             },
21425             "PropertyIsBetween": function(node, obj) {
21426                 var filter = new OpenLayers.Filter.Comparison({
21427                     type: OpenLayers.Filter.Comparison.BETWEEN
21428                 });
21429                 this.readChildNodes(node, filter);
21430                 obj.filters.push(filter);
21431             },
21432             "Literal": function(node, obj) {
21433                 obj.value = OpenLayers.String.numericIf(
21434                     this.getChildValue(node), true);
21435             },
21436             "PropertyName": function(node, filter) {
21437                 filter.property = this.getChildValue(node);
21438             },
21439             "LowerBoundary": function(node, filter) {
21440                 filter.lowerBoundary = OpenLayers.String.numericIf(
21441                     this.readers.ogc._expression.call(this, node), true);
21442             },
21443             "UpperBoundary": function(node, filter) {
21444                 filter.upperBoundary = OpenLayers.String.numericIf(
21445                     this.readers.ogc._expression.call(this, node), true);
21446             },
21447             "Intersects": function(node, obj) {
21448                 this.readSpatial(node, obj, OpenLayers.Filter.Spatial.INTERSECTS);
21449             },
21450             "Within": function(node, obj) {
21451                 this.readSpatial(node, obj, OpenLayers.Filter.Spatial.WITHIN);
21452             },
21453             "Contains": function(node, obj) {
21454                 this.readSpatial(node, obj, OpenLayers.Filter.Spatial.CONTAINS);
21455             },
21456             "DWithin": function(node, obj) {
21457                 this.readSpatial(node, obj, OpenLayers.Filter.Spatial.DWITHIN);
21458             },
21459             "Distance": function(node, obj) {
21460                 obj.distance = parseInt(this.getChildValue(node));
21461                 obj.distanceUnits = node.getAttribute("units");
21462             },
21463             "Function": function(node, obj) {
21464                 //TODO write decoder for it
21465                 return;
21466             },
21467             "PropertyIsNull": function(node, obj) {
21468                 var filter = new OpenLayers.Filter.Comparison({
21469                     type: OpenLayers.Filter.Comparison.IS_NULL
21470                 });
21471                 this.readChildNodes(node, filter);
21472                 obj.filters.push(filter);
21473             }
21474         }
21475     },
21476     
21477     /**
21478      * Method: readSpatial
21479      *
21480      * Read a {<OpenLayers.Filter.Spatial>} filter.
21481      * 
21482      * Parameters:
21483      * node - {DOMElement} A DOM element that contains an ogc:expression.
21484      * obj - {Object} The target object.
21485      * type - {String} One of the OpenLayers.Filter.Spatial.* constants.
21486      *
21487      * Returns:
21488      * {<OpenLayers.Filter.Spatial>} The created filter.
21489      */
21490     readSpatial: function(node, obj, type) {
21491         var filter = new OpenLayers.Filter.Spatial({
21492             type: type
21493         });
21494         this.readChildNodes(node, filter);
21495         filter.value = filter.components[0];
21496         delete filter.components;
21497         obj.filters.push(filter);
21498     },
21499
21500     /**
21501      * APIMethod: encodeLiteral
21502      * Generates the string representation of a value for use in <Literal> 
21503      *     elements.  The default encoder writes Date values as ISO 8601 
21504      *     strings.
21505      *
21506      * Parameters:
21507      * value - {Object} Literal value to encode
21508      *
21509      * Returns:
21510      * {String} String representation of the provided value.
21511      */
21512     encodeLiteral: function(value) {
21513         if (value instanceof Date) {
21514             value = OpenLayers.Date.toISOString(value);
21515         }
21516         return value;
21517     },
21518
21519     /**
21520      * Method: writeOgcExpression
21521      * Limited support for writing OGC expressions. Currently it supports
21522      * (<OpenLayers.Filter.Function> || String || Number)
21523      *
21524      * Parameters:
21525      * value - (<OpenLayers.Filter.Function> || String || Number)
21526      * node - {DOMElement} A parent DOM element 
21527      *
21528      * Returns:
21529      * {DOMElement} Updated node element.
21530      */
21531     writeOgcExpression: function(value, node) {
21532         if (value instanceof OpenLayers.Filter.Function){
21533             this.writeNode("Function", value, node);
21534         } else {
21535             this.writeNode("Literal", value, node);
21536         }
21537         return node;
21538     },    
21539     
21540     /**
21541      * Method: write
21542      *
21543      * Parameters:
21544      * filter - {<OpenLayers.Filter>} A filter object.
21545      *
21546      * Returns:
21547      * {DOMElement} An ogc:Filter element.
21548      */
21549     write: function(filter) {
21550         return this.writers.ogc["Filter"].apply(this, [filter]);
21551     },
21552     
21553     /**
21554      * Property: writers
21555      * As a compliment to the readers property, this structure contains public
21556      *     writing functions grouped by namespace alias and named like the
21557      *     node names they produce.
21558      */
21559     writers: {
21560         "ogc": {
21561             "Filter": function(filter) {
21562                 var node = this.createElementNSPlus("ogc:Filter");
21563                 this.writeNode(this.getFilterType(filter), filter, node);
21564                 return node;
21565             },
21566             "_featureIds": function(filter) {
21567                 var node = this.createDocumentFragment();
21568                 for (var i=0, ii=filter.fids.length; i<ii; ++i) {
21569                     this.writeNode("ogc:FeatureId", filter.fids[i], node);
21570                 }
21571                 return node;
21572             },
21573             "FeatureId": function(fid) {
21574                 return this.createElementNSPlus("ogc:FeatureId", {
21575                     attributes: {fid: fid}
21576                 });
21577             },
21578             "And": function(filter) {
21579                 var node = this.createElementNSPlus("ogc:And");
21580                 var childFilter;
21581                 for (var i=0, ii=filter.filters.length; i<ii; ++i) {
21582                     childFilter = filter.filters[i];
21583                     this.writeNode(
21584                         this.getFilterType(childFilter), childFilter, node
21585                     );
21586                 }
21587                 return node;
21588             },
21589             "Or": function(filter) {
21590                 var node = this.createElementNSPlus("ogc:Or");
21591                 var childFilter;
21592                 for (var i=0, ii=filter.filters.length; i<ii; ++i) {
21593                     childFilter = filter.filters[i];
21594                     this.writeNode(
21595                         this.getFilterType(childFilter), childFilter, node
21596                     );
21597                 }
21598                 return node;
21599             },
21600             "Not": function(filter) {
21601                 var node = this.createElementNSPlus("ogc:Not");
21602                 var childFilter = filter.filters[0];
21603                 this.writeNode(
21604                     this.getFilterType(childFilter), childFilter, node
21605                 );
21606                 return node;
21607             },
21608             "PropertyIsLessThan": function(filter) {
21609                 var node = this.createElementNSPlus("ogc:PropertyIsLessThan");
21610                 // no ogc:expression handling for PropertyName for now
21611                 this.writeNode("PropertyName", filter, node);
21612                 // handle Literals or Functions for now
21613                 this.writeOgcExpression(filter.value, node);
21614                 return node;
21615             },
21616             "PropertyIsGreaterThan": function(filter) {
21617                 var node = this.createElementNSPlus("ogc:PropertyIsGreaterThan");
21618                 // no ogc:expression handling for PropertyName for now
21619                 this.writeNode("PropertyName", filter, node);
21620                 // handle Literals or Functions for now
21621                 this.writeOgcExpression(filter.value, node);
21622                 return node;
21623             },
21624             "PropertyIsLessThanOrEqualTo": function(filter) {
21625                 var node = this.createElementNSPlus("ogc:PropertyIsLessThanOrEqualTo");
21626                 // no ogc:expression handling for PropertyName for now
21627                 this.writeNode("PropertyName", filter, node);
21628                 // handle Literals or Functions for now
21629                 this.writeOgcExpression(filter.value, node);
21630                 return node;
21631             },
21632             "PropertyIsGreaterThanOrEqualTo": function(filter) {
21633                 var node = this.createElementNSPlus("ogc:PropertyIsGreaterThanOrEqualTo");
21634                 // no ogc:expression handling for PropertyName for now
21635                 this.writeNode("PropertyName", filter, node);
21636                 // handle Literals or Functions for now
21637                 this.writeOgcExpression(filter.value, node);
21638                 return node;
21639             },
21640             "PropertyIsBetween": function(filter) {
21641                 var node = this.createElementNSPlus("ogc:PropertyIsBetween");
21642                 // no ogc:expression handling for PropertyName for now
21643                 this.writeNode("PropertyName", filter, node);
21644                 this.writeNode("LowerBoundary", filter, node);
21645                 this.writeNode("UpperBoundary", filter, node);
21646                 return node;
21647             },
21648             "PropertyName": function(filter) {
21649                 // no ogc:expression handling for now
21650                 return this.createElementNSPlus("ogc:PropertyName", {
21651                     value: filter.property
21652                 });
21653             },
21654             "Literal": function(value) {
21655                 var encode = this.encodeLiteral ||
21656                     OpenLayers.Format.Filter.v1.prototype.encodeLiteral;
21657                 return this.createElementNSPlus("ogc:Literal", {
21658                     value: encode(value)
21659                 });
21660             },
21661             "LowerBoundary": function(filter) {
21662                 // handle Literals or Functions for now
21663                 var node = this.createElementNSPlus("ogc:LowerBoundary");
21664                 this.writeOgcExpression(filter.lowerBoundary, node);
21665                 return node;
21666             },
21667             "UpperBoundary": function(filter) {
21668                 // handle Literals or Functions for now
21669                 var node = this.createElementNSPlus("ogc:UpperBoundary");
21670                 this.writeNode("Literal", filter.upperBoundary, node);
21671                 return node;
21672             },
21673             "INTERSECTS": function(filter) {
21674                 return this.writeSpatial(filter, "Intersects");
21675             },
21676             "WITHIN": function(filter) {
21677                 return this.writeSpatial(filter, "Within");
21678             },
21679             "CONTAINS": function(filter) {
21680                 return this.writeSpatial(filter, "Contains");
21681             },
21682             "DWITHIN": function(filter) {
21683                 var node = this.writeSpatial(filter, "DWithin");
21684                 this.writeNode("Distance", filter, node);
21685                 return node;
21686             },
21687             "Distance": function(filter) {
21688                 return this.createElementNSPlus("ogc:Distance", {
21689                     attributes: {
21690                         units: filter.distanceUnits
21691                     },
21692                     value: filter.distance
21693                 });
21694             },
21695             "Function": function(filter) {
21696                 var node = this.createElementNSPlus("ogc:Function", {
21697                     attributes: {
21698                         name: filter.name
21699                     }
21700                 });
21701                 var params = filter.params;
21702                 for(var i=0, len=params.length; i<len; i++){
21703                     this.writeOgcExpression(params[i], node);
21704                 }
21705                 return node;
21706             },
21707             "PropertyIsNull": function(filter) {
21708                 var node = this.createElementNSPlus("ogc:PropertyIsNull");
21709                 this.writeNode("PropertyName", filter, node);
21710                 return node;
21711             }
21712         }
21713     },
21714
21715     /**
21716      * Method: getFilterType
21717      */
21718     getFilterType: function(filter) {
21719         var filterType = this.filterMap[filter.type];
21720         if(!filterType) {
21721             throw "Filter writing not supported for rule type: " + filter.type;
21722         }
21723         return filterType;
21724     },
21725     
21726     /**
21727      * Property: filterMap
21728      * {Object} Contains a member for each filter type.  Values are node names
21729      *     for corresponding OGC Filter child elements.
21730      */
21731     filterMap: {
21732         "&&": "And",
21733         "||": "Or",
21734         "!": "Not",
21735         "==": "PropertyIsEqualTo",
21736         "!=": "PropertyIsNotEqualTo",
21737         "<": "PropertyIsLessThan",
21738         ">": "PropertyIsGreaterThan",
21739         "<=": "PropertyIsLessThanOrEqualTo",
21740         ">=": "PropertyIsGreaterThanOrEqualTo",
21741         "..": "PropertyIsBetween",
21742         "~": "PropertyIsLike",
21743         "NULL": "PropertyIsNull",
21744         "BBOX": "BBOX",
21745         "DWITHIN": "DWITHIN",
21746         "WITHIN": "WITHIN",
21747         "CONTAINS": "CONTAINS",
21748         "INTERSECTS": "INTERSECTS",
21749         "FID": "_featureIds"
21750     },
21751
21752     CLASS_NAME: "OpenLayers.Format.Filter.v1" 
21753
21754 });
21755 /* ======================================================================
21756     OpenLayers/Format/Filter/v1_0_0.js
21757    ====================================================================== */
21758
21759 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
21760  * full list of contributors). Published under the 2-clause BSD license.
21761  * See license.txt in the OpenLayers distribution or repository for the
21762  * full text of the license. */
21763
21764 /**
21765  * @requires OpenLayers/Format/GML/v2.js
21766  * @requires OpenLayers/Format/Filter/v1.js
21767  */
21768
21769 /**
21770  * Class: OpenLayers.Format.Filter.v1_0_0
21771  * Write ogc:Filter version 1.0.0.
21772  * 
21773  * Inherits from:
21774  *  - <OpenLayers.Format.GML.v2>
21775  *  - <OpenLayers.Format.Filter.v1>
21776  */
21777 OpenLayers.Format.Filter.v1_0_0 = OpenLayers.Class(
21778     OpenLayers.Format.GML.v2, OpenLayers.Format.Filter.v1, {
21779     
21780     /**
21781      * Constant: VERSION
21782      * {String} 1.0.0
21783      */
21784     VERSION: "1.0.0",
21785     
21786     /**
21787      * Property: schemaLocation
21788      * {String} http://www.opengis.net/ogc/filter/1.0.0/filter.xsd
21789      */
21790     schemaLocation: "http://www.opengis.net/ogc/filter/1.0.0/filter.xsd",
21791
21792     /**
21793      * Constructor: OpenLayers.Format.Filter.v1_0_0
21794      * Instances of this class are not created directly.  Use the
21795      *     <OpenLayers.Format.Filter> constructor instead.
21796      *
21797      * Parameters:
21798      * options - {Object} An optional object whose properties will be set on
21799      *     this instance.
21800      */
21801     initialize: function(options) {
21802         OpenLayers.Format.GML.v2.prototype.initialize.apply(
21803             this, [options]
21804         );
21805     },
21806
21807     /**
21808      * Property: readers
21809      * Contains public functions, grouped by namespace prefix, that will
21810      *     be applied when a namespaced node is found matching the function
21811      *     name.  The function will be applied in the scope of this parser
21812      *     with two arguments: the node being read and a context object passed
21813      *     from the parent.
21814      */
21815     readers: {
21816         "ogc": OpenLayers.Util.applyDefaults({
21817             "PropertyIsEqualTo": function(node, obj) {
21818                 var filter = new OpenLayers.Filter.Comparison({
21819                     type: OpenLayers.Filter.Comparison.EQUAL_TO
21820                 });
21821                 this.readChildNodes(node, filter);
21822                 obj.filters.push(filter);
21823             },
21824             "PropertyIsNotEqualTo": function(node, obj) {
21825                 var filter = new OpenLayers.Filter.Comparison({
21826                     type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO
21827                 });
21828                 this.readChildNodes(node, filter);
21829                 obj.filters.push(filter);
21830             },
21831             "PropertyIsLike": function(node, obj) {
21832                 var filter = new OpenLayers.Filter.Comparison({
21833                     type: OpenLayers.Filter.Comparison.LIKE
21834                 });
21835                 this.readChildNodes(node, filter);
21836                 var wildCard = node.getAttribute("wildCard");
21837                 var singleChar = node.getAttribute("singleChar");
21838                 var esc = node.getAttribute("escape");
21839                 filter.value2regex(wildCard, singleChar, esc);
21840                 obj.filters.push(filter);
21841             }
21842         }, OpenLayers.Format.Filter.v1.prototype.readers["ogc"]),
21843         "gml": OpenLayers.Format.GML.v2.prototype.readers["gml"],
21844         "feature": OpenLayers.Format.GML.v2.prototype.readers["feature"]        
21845     },
21846
21847     /**
21848      * Property: writers
21849      * As a compliment to the readers property, this structure contains public
21850      *     writing functions grouped by namespace alias and named like the
21851      *     node names they produce.
21852      */
21853     writers: {
21854         "ogc": OpenLayers.Util.applyDefaults({
21855             "PropertyIsEqualTo": function(filter) {
21856                 var node = this.createElementNSPlus("ogc:PropertyIsEqualTo");
21857                 // no ogc:expression handling for PropertyName for now
21858                 this.writeNode("PropertyName", filter, node);
21859                 // handle Literals or Functions for now
21860                 this.writeOgcExpression(filter.value, node);
21861                 return node;
21862             },
21863             "PropertyIsNotEqualTo": function(filter) {
21864                 var node = this.createElementNSPlus("ogc:PropertyIsNotEqualTo");
21865                 // no ogc:expression handling for PropertyName for now
21866                 this.writeNode("PropertyName", filter, node);
21867                 // handle Literals or Functions for now
21868                 this.writeOgcExpression(filter.value, node);
21869                 return node;
21870             },
21871             "PropertyIsLike": function(filter) {
21872                 var node = this.createElementNSPlus("ogc:PropertyIsLike", {
21873                     attributes: {
21874                         wildCard: "*", singleChar: ".", escape: "!"
21875                     }
21876                 });
21877                 // no ogc:expression handling for now
21878                 this.writeNode("PropertyName", filter, node);
21879                 // convert regex string to ogc string
21880                 this.writeNode("Literal", filter.regex2value(), node);
21881                 return node;
21882             },
21883             "BBOX": function(filter) {
21884                 var node = this.createElementNSPlus("ogc:BBOX");
21885                 // PropertyName is mandatory in 1.0.0, but e.g. GeoServer also
21886                 // accepts filters without it. When this is used with
21887                 // OpenLayers.Protocol.WFS, OpenLayers.Format.WFST will set a
21888                 // missing filter.property to the geometryName that is
21889                 // configured with the protocol, which defaults to "the_geom".
21890                 // So the only way to omit this mandatory property is to not
21891                 // set the property on the filter and to set the geometryName
21892                 // on the WFS protocol to null. The latter also happens when
21893                 // the protocol is configured without a geometryName and a
21894                 // featureNS.
21895                 filter.property && this.writeNode("PropertyName", filter, node);
21896                 var box = this.writeNode("gml:Box", filter.value, node);
21897                 if(filter.projection) {
21898                     box.setAttribute("srsName", filter.projection);
21899                 }
21900                 return node;
21901             }
21902         }, OpenLayers.Format.Filter.v1.prototype.writers["ogc"]),
21903         "gml": OpenLayers.Format.GML.v2.prototype.writers["gml"],
21904         "feature": OpenLayers.Format.GML.v2.prototype.writers["feature"]
21905     },
21906
21907     /**
21908      * Method: writeSpatial
21909      *
21910      * Read a {<OpenLayers.Filter.Spatial>} filter and converts it into XML.
21911      *
21912      * Parameters:
21913      * filter - {<OpenLayers.Filter.Spatial>} The filter.
21914      * name - {String} Name of the generated XML element.
21915      *
21916      * Returns:
21917      * {DOMElement} The created XML element.
21918      */
21919     writeSpatial: function(filter, name) {
21920         var node = this.createElementNSPlus("ogc:"+name);
21921         this.writeNode("PropertyName", filter, node);
21922         if(filter.value instanceof OpenLayers.Filter.Function) {
21923             this.writeNode("Function", filter.value, node);
21924         } else {
21925         var child;
21926         if(filter.value instanceof OpenLayers.Geometry) {
21927             child = this.writeNode("feature:_geometry", filter.value).firstChild;
21928         } else {
21929             child = this.writeNode("gml:Box", filter.value);
21930         }
21931         if(filter.projection) {
21932             child.setAttribute("srsName", filter.projection);
21933         }
21934         node.appendChild(child);
21935         }
21936         return node;
21937     },
21938
21939
21940     CLASS_NAME: "OpenLayers.Format.Filter.v1_0_0" 
21941
21942 });
21943 /* ======================================================================
21944     OpenLayers/Format/WFST/v1_0_0.js
21945    ====================================================================== */
21946
21947 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
21948  * full list of contributors). Published under the 2-clause BSD license.
21949  * See license.txt in the OpenLayers distribution or repository for the
21950  * full text of the license. */
21951
21952 /**
21953  * @requires OpenLayers/Format/WFST/v1.js
21954  * @requires OpenLayers/Format/Filter/v1_0_0.js
21955  */
21956
21957 /**
21958  * Class: OpenLayers.Format.WFST.v1_0_0
21959  * A format for creating WFS v1.0.0 transactions.  Create a new instance with the
21960  *     <OpenLayers.Format.WFST.v1_0_0> constructor.
21961  *
21962  * Inherits from:
21963  *  - <OpenLayers.Format.Filter.v1_0_0>
21964  *  - <OpenLayers.Format.WFST.v1>
21965  */
21966 OpenLayers.Format.WFST.v1_0_0 = OpenLayers.Class(
21967     OpenLayers.Format.Filter.v1_0_0, OpenLayers.Format.WFST.v1, {
21968
21969     /**
21970      * Property: version
21971      * {String} WFS version number.
21972      */
21973     version: "1.0.0",
21974
21975     /**
21976      * APIProperty: srsNameInQuery
21977      * {Boolean} If true the reference system is passed in Query requests
21978      *     via the "srsName" attribute to the "wfs:Query" element, this
21979      *     property defaults to false as it isn't WFS 1.0.0 compliant.
21980      */
21981     srsNameInQuery: false,
21982
21983     /**
21984      * Property: schemaLocations
21985      * {Object} Properties are namespace aliases, values are schema locations.
21986      */
21987     schemaLocations: {
21988         "wfs": "http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd"
21989     },
21990
21991     /**
21992      * Constructor: OpenLayers.Format.WFST.v1_0_0
21993      * A class for parsing and generating WFS v1.0.0 transactions.
21994      *
21995      * Parameters:
21996      * options - {Object} Optional object whose properties will be set on the
21997      *     instance.
21998      *
21999      * Valid options properties:
22000      * featureType - {String} Local (without prefix) feature typeName (required).
22001      * featureNS - {String} Feature namespace (optional).
22002      * featurePrefix - {String} Feature namespace alias (optional - only used
22003      *     if featureNS is provided).  Default is 'feature'.
22004      * geometryName - {String} Name of geometry attribute.  Default is 'the_geom'.
22005      */
22006     initialize: function(options) {
22007         OpenLayers.Format.Filter.v1_0_0.prototype.initialize.apply(this, [options]);
22008         OpenLayers.Format.WFST.v1.prototype.initialize.apply(this, [options]);
22009     },
22010
22011     /**
22012      * Method: readNode
22013      * Shorthand for applying one of the named readers given the node
22014      *     namespace and local name.  Readers take two args (node, obj) and
22015      *     generally extend or modify the second.
22016      *
22017      * Parameters:
22018      * node - {DOMElement} The node to be read (required).
22019      * obj - {Object} The object to be modified (optional).
22020      * first - {Boolean} Should be set to true for the first node read. This
22021      *     is usually the readNode call in the read method. Without this being
22022      *     set, auto-configured properties will stick on subsequent reads.
22023      *
22024      * Returns:
22025      * {Object} The input object, modified (or a new one if none was provided).
22026      */
22027     readNode: function(node, obj, first) {
22028         // Not the superclass, only the mixin classes inherit from
22029         // Format.GML.v2. We need this because we don't want to get readNode
22030         // from the superclass's superclass, which is OpenLayers.Format.XML.
22031         return OpenLayers.Format.GML.v2.prototype.readNode.apply(this, arguments);
22032     },
22033
22034     /**
22035      * Property: readers
22036      * Contains public functions, grouped by namespace prefix, that will
22037      *     be applied when a namespaced node is found matching the function
22038      *     name.  The function will be applied in the scope of this parser
22039      *     with two arguments: the node being read and a context object passed
22040      *     from the parent.
22041      */
22042     readers: {
22043         "wfs": OpenLayers.Util.applyDefaults({
22044             "WFS_TransactionResponse": function(node, obj) {
22045                 obj.insertIds = [];
22046                 obj.success = false;
22047                 this.readChildNodes(node, obj);
22048             },
22049             "InsertResult": function(node, container) {
22050                 var obj = {fids: []};
22051                 this.readChildNodes(node, obj);
22052                 container.insertIds = container.insertIds.concat(obj.fids);
22053             },
22054             "TransactionResult": function(node, obj) {
22055                 this.readChildNodes(node, obj);
22056             },
22057             "Status": function(node, obj) {
22058                 this.readChildNodes(node, obj);
22059             },
22060             "SUCCESS": function(node, obj) {
22061                 obj.success = true;
22062             }
22063         }, OpenLayers.Format.WFST.v1.prototype.readers["wfs"]),
22064         "gml": OpenLayers.Format.GML.v2.prototype.readers["gml"],
22065         "feature": OpenLayers.Format.GML.v2.prototype.readers["feature"],
22066         "ogc": OpenLayers.Format.Filter.v1_0_0.prototype.readers["ogc"]
22067     },
22068
22069     /**
22070      * Property: writers
22071      * As a compliment to the readers property, this structure contains public
22072      *     writing functions grouped by namespace alias and named like the
22073      *     node names they produce.
22074      */
22075     writers: {
22076         "wfs": OpenLayers.Util.applyDefaults({
22077             "Query": function(options) {
22078                 options = OpenLayers.Util.extend({
22079                     featureNS: this.featureNS,
22080                     featurePrefix: this.featurePrefix,
22081                     featureType: this.featureType,
22082                     srsName: this.srsName,
22083                     srsNameInQuery: this.srsNameInQuery
22084                 }, options);
22085                 var prefix = options.featurePrefix;
22086                 var node = this.createElementNSPlus("wfs:Query", {
22087                     attributes: {
22088                         typeName: (options.featureNS ? prefix + ":" : "") +
22089                             options.featureType
22090                     }
22091                 });
22092                 if(options.srsNameInQuery && options.srsName) {
22093                     node.setAttribute("srsName", options.srsName);
22094                 }
22095                 if(options.featureNS) {
22096                     this.setAttributeNS(
22097                         node, this.namespaces.xmlns,
22098                         "xmlns:" + prefix, options.featureNS
22099                     );
22100                 }
22101                 if(options.propertyNames) {
22102                     for(var i=0,len = options.propertyNames.length; i<len; i++) {
22103                         this.writeNode(
22104                             "ogc:PropertyName",
22105                             {property: options.propertyNames[i]},
22106                             node
22107                         );
22108                     }
22109                 }
22110                 if(options.filter) {
22111                     this.setFilterProperty(options.filter);
22112                     this.writeNode("ogc:Filter", options.filter, node);
22113                 }
22114                 return node;
22115             }
22116         }, OpenLayers.Format.WFST.v1.prototype.writers["wfs"]),
22117         "gml": OpenLayers.Format.GML.v2.prototype.writers["gml"],
22118         "feature": OpenLayers.Format.GML.v2.prototype.writers["feature"],
22119         "ogc": OpenLayers.Format.Filter.v1_0_0.prototype.writers["ogc"]
22120     },
22121
22122     CLASS_NAME: "OpenLayers.Format.WFST.v1_0_0"
22123 });
22124 /* ======================================================================
22125     OpenLayers/Protocol/WFS/v1_0_0.js
22126    ====================================================================== */
22127
22128 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
22129  * full list of contributors). Published under the 2-clause BSD license.
22130  * See license.txt in the OpenLayers distribution or repository for the
22131  * full text of the license. */
22132
22133 /**
22134  * @requires OpenLayers/Protocol/WFS/v1.js
22135  * @requires OpenLayers/Format/WFST/v1_0_0.js
22136  */
22137
22138 /**
22139  * Class: OpenLayers.Protocol.WFS.v1_0_0
22140  * A WFS v1.0.0 protocol for vector layers.  Create a new instance with the
22141  *     <OpenLayers.Protocol.WFS.v1_0_0> constructor.
22142  *
22143  * Inherits from:
22144  *  - <OpenLayers.Protocol.WFS.v1>
22145  */
22146 OpenLayers.Protocol.WFS.v1_0_0 = OpenLayers.Class(OpenLayers.Protocol.WFS.v1, {
22147     
22148     /**
22149      * Property: version
22150      * {String} WFS version number.
22151      */
22152     version: "1.0.0",
22153     
22154     /**
22155      * Constructor: OpenLayers.Protocol.WFS.v1_0_0
22156      * A class for giving layers WFS v1.0.0 protocol.
22157      *
22158      * Parameters:
22159      * options - {Object} Optional object whose properties will be set on the
22160      *     instance.
22161      *
22162      * Valid options properties:
22163      * featureType - {String} Local (without prefix) feature typeName (required).
22164      * featureNS - {String} Feature namespace (optional).
22165      * featurePrefix - {String} Feature namespace alias (optional - only used
22166      *     if featureNS is provided).  Default is 'feature'.
22167      * geometryName - {String} Name of geometry attribute.  Default is 'the_geom'.
22168      */
22169    
22170     CLASS_NAME: "OpenLayers.Protocol.WFS.v1_0_0" 
22171 });
22172 /* ======================================================================
22173     OpenLayers/Control.js
22174    ====================================================================== */
22175
22176 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
22177  * full list of contributors). Published under the 2-clause BSD license.
22178  * See license.txt in the OpenLayers distribution or repository for the
22179  * full text of the license. */
22180
22181 /**
22182  * @requires OpenLayers/BaseTypes/Class.js
22183  */
22184
22185 /**
22186  * Class: OpenLayers.Control
22187  * Controls affect the display or behavior of the map. They allow everything
22188  * from panning and zooming to displaying a scale indicator. Controls by 
22189  * default are added to the map they are contained within however it is
22190  * possible to add a control to an external div by passing the div in the
22191  * options parameter.
22192  * 
22193  * Example:
22194  * The following example shows how to add many of the common controls
22195  * to a map.
22196  * 
22197  * > var map = new OpenLayers.Map('map', { controls: [] });
22198  * >
22199  * > map.addControl(new OpenLayers.Control.PanZoomBar());
22200  * > map.addControl(new OpenLayers.Control.LayerSwitcher({'ascending':false}));
22201  * > map.addControl(new OpenLayers.Control.Permalink());
22202  * > map.addControl(new OpenLayers.Control.Permalink('permalink'));
22203  * > map.addControl(new OpenLayers.Control.MousePosition());
22204  * > map.addControl(new OpenLayers.Control.OverviewMap());
22205  * > map.addControl(new OpenLayers.Control.KeyboardDefaults());
22206  *
22207  * The next code fragment is a quick example of how to intercept 
22208  * shift-mouse click to display the extent of the bounding box
22209  * dragged out by the user.  Usually controls are not created
22210  * in exactly this manner.  See the source for a more complete 
22211  * example:
22212  *
22213  * > var control = new OpenLayers.Control();
22214  * > OpenLayers.Util.extend(control, {
22215  * >     draw: function () {
22216  * >         // this Handler.Box will intercept the shift-mousedown
22217  * >         // before Control.MouseDefault gets to see it
22218  * >         this.box = new OpenLayers.Handler.Box( control, 
22219  * >             {"done": this.notice},
22220  * >             {keyMask: OpenLayers.Handler.MOD_SHIFT});
22221  * >         this.box.activate();
22222  * >     },
22223  * >
22224  * >     notice: function (bounds) {
22225  * >         OpenLayers.Console.userError(bounds);
22226  * >     }
22227  * > }); 
22228  * > map.addControl(control);
22229  * 
22230  */
22231 OpenLayers.Control = OpenLayers.Class({
22232
22233     /** 
22234      * Property: id 
22235      * {String} 
22236      */
22237     id: null,
22238     
22239     /** 
22240      * Property: map 
22241      * {<OpenLayers.Map>} this gets set in the addControl() function in
22242      * OpenLayers.Map 
22243      */
22244     map: null,
22245
22246     /** 
22247      * APIProperty: div 
22248      * {DOMElement} The element that contains the control, if not present the 
22249      *     control is placed inside the map.
22250      */
22251     div: null,
22252
22253     /** 
22254      * APIProperty: type 
22255      * {Number} Controls can have a 'type'. The type determines the type of
22256      * interactions which are possible with them when they are placed in an
22257      * <OpenLayers.Control.Panel>. 
22258      */
22259     type: null, 
22260
22261     /** 
22262      * Property: allowSelection
22263      * {Boolean} By default, controls do not allow selection, because
22264      * it may interfere with map dragging. If this is true, OpenLayers
22265      * will not prevent selection of the control.
22266      * Default is false.
22267      */
22268     allowSelection: false,  
22269
22270     /** 
22271      * Property: displayClass 
22272      * {string}  This property is used for CSS related to the drawing of the
22273      * Control. 
22274      */
22275     displayClass: "",
22276     
22277     /**
22278     * APIProperty: title  
22279     * {string}  This property is used for showing a tooltip over the  
22280     * Control.  
22281     */ 
22282     title: "",
22283
22284     /**
22285      * APIProperty: autoActivate
22286      * {Boolean} Activate the control when it is added to a map.  Default is
22287      *     false.
22288      */
22289     autoActivate: false,
22290
22291     /** 
22292      * APIProperty: active 
22293      * {Boolean} The control is active (read-only).  Use <activate> and 
22294      *     <deactivate> to change control state.
22295      */
22296     active: null,
22297
22298     /**
22299      * Property: handlerOptions
22300      * {Object} Used to set non-default properties on the control's handler
22301      */
22302     handlerOptions: null,
22303
22304     /** 
22305      * Property: handler 
22306      * {<OpenLayers.Handler>} null
22307      */
22308     handler: null,
22309
22310     /**
22311      * APIProperty: eventListeners
22312      * {Object} If set as an option at construction, the eventListeners
22313      *     object will be registered with <OpenLayers.Events.on>.  Object
22314      *     structure must be a listeners object as shown in the example for
22315      *     the events.on method.
22316      */
22317     eventListeners: null,
22318
22319     /** 
22320      * APIProperty: events
22321      * {<OpenLayers.Events>} Events instance for listeners and triggering
22322      *     control specific events.
22323      *
22324      * Register a listener for a particular event with the following syntax:
22325      * (code)
22326      * control.events.register(type, obj, listener);
22327      * (end)
22328      *
22329      * Listeners will be called with a reference to an event object.  The
22330      *     properties of this event depends on exactly what happened.
22331      *
22332      * All event objects have at least the following properties:
22333      * object - {Object} A reference to control.events.object (a reference
22334      *      to the control).
22335      * element - {DOMElement} A reference to control.events.element (which
22336      *      will be null unless documented otherwise).
22337      *
22338      * Supported map event types:
22339      * activate - Triggered when activated.
22340      * deactivate - Triggered when deactivated.
22341      */
22342     events: null,
22343
22344     /**
22345      * Constructor: OpenLayers.Control
22346      * Create an OpenLayers Control.  The options passed as a parameter
22347      * directly extend the control.  For example passing the following:
22348      * 
22349      * > var control = new OpenLayers.Control({div: myDiv});
22350      *
22351      * Overrides the default div attribute value of null.
22352      * 
22353      * Parameters:
22354      * options - {Object} 
22355      */
22356     initialize: function (options) {
22357         // We do this before the extend so that instances can override
22358         // className in options.
22359         this.displayClass = 
22360             this.CLASS_NAME.replace("OpenLayers.", "ol").replace(/\./g, "");
22361         
22362         OpenLayers.Util.extend(this, options);
22363         
22364         this.events = new OpenLayers.Events(this);
22365         if(this.eventListeners instanceof Object) {
22366             this.events.on(this.eventListeners);
22367         }
22368         if (this.id == null) {
22369             this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
22370         }
22371     },
22372
22373     /**
22374      * Method: destroy
22375      * The destroy method is used to perform any clean up before the control
22376      * is dereferenced.  Typically this is where event listeners are removed
22377      * to prevent memory leaks.
22378      */
22379     destroy: function () {
22380         if(this.events) {
22381             if(this.eventListeners) {
22382                 this.events.un(this.eventListeners);
22383             }
22384             this.events.destroy();
22385             this.events = null;
22386         }
22387         this.eventListeners = null;
22388
22389         // eliminate circular references
22390         if (this.handler) {
22391             this.handler.destroy();
22392             this.handler = null;
22393         }
22394         if(this.handlers) {
22395             for(var key in this.handlers) {
22396                 if(this.handlers.hasOwnProperty(key) &&
22397                    typeof this.handlers[key].destroy == "function") {
22398                     this.handlers[key].destroy();
22399                 }
22400             }
22401             this.handlers = null;
22402         }
22403         if (this.map) {
22404             this.map.removeControl(this);
22405             this.map = null;
22406         }
22407         this.div = null;
22408     },
22409
22410     /** 
22411      * Method: setMap
22412      * Set the map property for the control. This is done through an accessor
22413      * so that subclasses can override this and take special action once 
22414      * they have their map variable set. 
22415      *
22416      * Parameters:
22417      * map - {<OpenLayers.Map>} 
22418      */
22419     setMap: function(map) {
22420         this.map = map;
22421         if (this.handler) {
22422             this.handler.setMap(map);
22423         }
22424     },
22425   
22426     /**
22427      * Method: draw
22428      * The draw method is called when the control is ready to be displayed
22429      * on the page.  If a div has not been created one is created.  Controls
22430      * with a visual component will almost always want to override this method 
22431      * to customize the look of control. 
22432      *
22433      * Parameters:
22434      * px - {<OpenLayers.Pixel>} The top-left pixel position of the control
22435      *      or null.
22436      *
22437      * Returns:
22438      * {DOMElement} A reference to the DIV DOMElement containing the control
22439      */
22440     draw: function (px) {
22441         if (this.div == null) {
22442             this.div = OpenLayers.Util.createDiv(this.id);
22443             this.div.className = this.displayClass;
22444             if (!this.allowSelection) {
22445                 this.div.className += " olControlNoSelect";
22446                 this.div.setAttribute("unselectable", "on", 0);
22447                 this.div.onselectstart = OpenLayers.Function.False; 
22448             }    
22449             if (this.title != "") {
22450                 this.div.title = this.title;
22451             }
22452         }
22453         if (px != null) {
22454             this.position = px.clone();
22455         }
22456         this.moveTo(this.position);
22457         return this.div;
22458     },
22459
22460     /**
22461      * Method: moveTo
22462      * Sets the left and top style attributes to the passed in pixel 
22463      * coordinates.
22464      *
22465      * Parameters:
22466      * px - {<OpenLayers.Pixel>}
22467      */
22468     moveTo: function (px) {
22469         if ((px != null) && (this.div != null)) {
22470             this.div.style.left = px.x + "px";
22471             this.div.style.top = px.y + "px";
22472         }
22473     },
22474
22475     /**
22476      * APIMethod: activate
22477      * Explicitly activates a control and its associated
22478      * handler if one has been set.  Controls can be
22479      * deactivated by calling the deactivate() method.
22480      * 
22481      * Returns:
22482      * {Boolean}  True if the control was successfully activated or
22483      *            false if the control was already active.
22484      */
22485     activate: function () {
22486         if (this.active) {
22487             return false;
22488         }
22489         if (this.handler) {
22490             this.handler.activate();
22491         }
22492         this.active = true;
22493         if(this.map) {
22494             OpenLayers.Element.addClass(
22495                 this.map.viewPortDiv,
22496                 this.displayClass.replace(/ /g, "") + "Active"
22497             );
22498         }
22499         this.events.triggerEvent("activate");
22500         return true;
22501     },
22502     
22503     /**
22504      * APIMethod: deactivate
22505      * Deactivates a control and its associated handler if any.  The exact
22506      * effect of this depends on the control itself.
22507      * 
22508      * Returns:
22509      * {Boolean} True if the control was effectively deactivated or false
22510      *           if the control was already inactive.
22511      */
22512     deactivate: function () {
22513         if (this.active) {
22514             if (this.handler) {
22515                 this.handler.deactivate();
22516             }
22517             this.active = false;
22518             if(this.map) {
22519                 OpenLayers.Element.removeClass(
22520                     this.map.viewPortDiv,
22521                     this.displayClass.replace(/ /g, "") + "Active"
22522                 );
22523             }
22524             this.events.triggerEvent("deactivate");
22525             return true;
22526         }
22527         return false;
22528     },
22529
22530     CLASS_NAME: "OpenLayers.Control"
22531 });
22532
22533 /**
22534  * Constant: OpenLayers.Control.TYPE_BUTTON
22535  */
22536 OpenLayers.Control.TYPE_BUTTON = 1;
22537
22538 /**
22539  * Constant: OpenLayers.Control.TYPE_TOGGLE
22540  */
22541 OpenLayers.Control.TYPE_TOGGLE = 2;
22542
22543 /**
22544  * Constant: OpenLayers.Control.TYPE_TOOL
22545  */
22546 OpenLayers.Control.TYPE_TOOL   = 3;
22547 /* ======================================================================
22548     OpenLayers/Handler.js
22549    ====================================================================== */
22550
22551 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
22552  * full list of contributors). Published under the 2-clause BSD license.
22553  * See license.txt in the OpenLayers distribution or repository for the
22554  * full text of the license. */
22555
22556 /**
22557  * @requires OpenLayers/BaseTypes/Class.js
22558  * @requires OpenLayers/Events.js
22559  */
22560
22561 /**
22562  * Class: OpenLayers.Handler
22563  * Base class to construct a higher-level handler for event sequences.  All
22564  *     handlers have activate and deactivate methods.  In addition, they have
22565  *     methods named like browser events.  When a handler is activated, any
22566  *     additional methods named like a browser event is registered as a
22567  *     listener for the corresponding event.  When a handler is deactivated,
22568  *     those same methods are unregistered as event listeners.
22569  *
22570  * Handlers also typically have a callbacks object with keys named like
22571  *     the abstracted events or event sequences that they are in charge of
22572  *     handling.  The controls that wrap handlers define the methods that
22573  *     correspond to these abstract events - so instead of listening for
22574  *     individual browser events, they only listen for the abstract events
22575  *     defined by the handler.
22576  *     
22577  * Handlers are created by controls, which ultimately have the responsibility
22578  *     of making changes to the the state of the application.  Handlers
22579  *     themselves may make temporary changes, but in general are expected to
22580  *     return the application in the same state that they found it.
22581  */
22582 OpenLayers.Handler = OpenLayers.Class({
22583
22584     /**
22585      * Property: id
22586      * {String}
22587      */
22588     id: null,
22589         
22590     /**
22591      * APIProperty: control
22592      * {<OpenLayers.Control>}. The control that initialized this handler.  The
22593      *     control is assumed to have a valid map property - that map is used
22594      *     in the handler's own setMap method.
22595      */
22596     control: null,
22597
22598     /**
22599      * Property: map
22600      * {<OpenLayers.Map>}
22601      */
22602     map: null,
22603
22604     /**
22605      * APIProperty: keyMask
22606      * {Integer} Use bitwise operators and one or more of the OpenLayers.Handler
22607      *     constants to construct a keyMask.  The keyMask is used by
22608      *     <checkModifiers>.  If the keyMask matches the combination of keys
22609      *     down on an event, checkModifiers returns true.
22610      *
22611      * Example:
22612      * (code)
22613      *     // handler only responds if the Shift key is down
22614      *     handler.keyMask = OpenLayers.Handler.MOD_SHIFT;
22615      *
22616      *     // handler only responds if Ctrl-Shift is down
22617      *     handler.keyMask = OpenLayers.Handler.MOD_SHIFT |
22618      *                       OpenLayers.Handler.MOD_CTRL;
22619      * (end)
22620      */
22621     keyMask: null,
22622
22623     /**
22624      * Property: active
22625      * {Boolean}
22626      */
22627     active: false,
22628     
22629     /**
22630      * Property: evt
22631      * {Event} This property references the last event handled by the handler.
22632      *     Note that this property is not part of the stable API.  Use of the
22633      *     evt property should be restricted to controls in the library
22634      *     or other applications that are willing to update with changes to
22635      *     the OpenLayers code.
22636      */
22637     evt: null,
22638     
22639     /**
22640      * Property: touch
22641      * {Boolean} Indicates the support of touch events. When touch events are 
22642      *     started touch will be true and all mouse related listeners will do 
22643      *     nothing.
22644      */
22645     touch: false,
22646
22647     /**
22648      * Constructor: OpenLayers.Handler
22649      * Construct a handler.
22650      *
22651      * Parameters:
22652      * control - {<OpenLayers.Control>} The control that initialized this
22653      *     handler.  The control is assumed to have a valid map property; that
22654      *     map is used in the handler's own setMap method.  If a map property
22655      *     is present in the options argument it will be used instead.
22656      * callbacks - {Object} An object whose properties correspond to abstracted
22657      *     events or sequences of browser events.  The values for these
22658      *     properties are functions defined by the control that get called by
22659      *     the handler.
22660      * options - {Object} An optional object whose properties will be set on
22661      *     the handler.
22662      */
22663     initialize: function(control, callbacks, options) {
22664         OpenLayers.Util.extend(this, options);
22665         this.control = control;
22666         this.callbacks = callbacks;
22667
22668         var map = this.map || control.map;
22669         if (map) {
22670             this.setMap(map); 
22671         }
22672         
22673         this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
22674     },
22675     
22676     /**
22677      * Method: setMap
22678      */
22679     setMap: function (map) {
22680         this.map = map;
22681     },
22682
22683     /**
22684      * Method: checkModifiers
22685      * Check the keyMask on the handler.  If no <keyMask> is set, this always
22686      *     returns true.  If a <keyMask> is set and it matches the combination
22687      *     of keys down on an event, this returns true.
22688      *
22689      * Returns:
22690      * {Boolean} The keyMask matches the keys down on an event.
22691      */
22692     checkModifiers: function (evt) {
22693         if(this.keyMask == null) {
22694             return true;
22695         }
22696         /* calculate the keyboard modifier mask for this event */
22697         var keyModifiers =
22698             (evt.shiftKey ? OpenLayers.Handler.MOD_SHIFT : 0) |
22699             (evt.ctrlKey  ? OpenLayers.Handler.MOD_CTRL  : 0) |
22700             (evt.altKey   ? OpenLayers.Handler.MOD_ALT   : 0) |
22701             (evt.metaKey  ? OpenLayers.Handler.MOD_META  : 0);
22702     
22703         /* if it differs from the handler object's key mask,
22704            bail out of the event handler */
22705         return (keyModifiers == this.keyMask);
22706     },
22707
22708     /**
22709      * APIMethod: activate
22710      * Turn on the handler.  Returns false if the handler was already active.
22711      * 
22712      * Returns: 
22713      * {Boolean} The handler was activated.
22714      */
22715     activate: function() {
22716         if(this.active) {
22717             return false;
22718         }
22719         // register for event handlers defined on this class.
22720         var events = OpenLayers.Events.prototype.BROWSER_EVENTS;
22721         for (var i=0, len=events.length; i<len; i++) {
22722             if (this[events[i]]) {
22723                 this.register(events[i], this[events[i]]); 
22724             }
22725         } 
22726         this.active = true;
22727         return true;
22728     },
22729     
22730     /**
22731      * APIMethod: deactivate
22732      * Turn off the handler.  Returns false if the handler was already inactive.
22733      * 
22734      * Returns:
22735      * {Boolean} The handler was deactivated.
22736      */
22737     deactivate: function() {
22738         if(!this.active) {
22739             return false;
22740         }
22741         // unregister event handlers defined on this class.
22742         var events = OpenLayers.Events.prototype.BROWSER_EVENTS;
22743         for (var i=0, len=events.length; i<len; i++) {
22744             if (this[events[i]]) {
22745                 this.unregister(events[i], this[events[i]]); 
22746             }
22747         } 
22748         this.touch = false;
22749         this.active = false;
22750         return true;
22751     },
22752
22753     /**
22754      * Method: startTouch
22755      * Start touch events, this method must be called by subclasses in 
22756      *     "touchstart" method. When touch events are started <touch> will be
22757      *     true and all mouse related listeners will do nothing.
22758      */
22759     startTouch: function() {
22760         if (!this.touch) {
22761             this.touch = true;
22762             var events = [
22763                 "mousedown", "mouseup", "mousemove", "click", "dblclick",
22764                 "mouseout"
22765             ];
22766             for (var i=0, len=events.length; i<len; i++) {
22767                 if (this[events[i]]) {
22768                     this.unregister(events[i], this[events[i]]); 
22769                 }
22770             } 
22771         }
22772     },
22773
22774     /**
22775     * Method: callback
22776     * Trigger the control's named callback with the given arguments
22777     *
22778     * Parameters:
22779     * name - {String} The key for the callback that is one of the properties
22780     *     of the handler's callbacks object.
22781     * args - {Array(*)} An array of arguments (any type) with which to call 
22782     *     the callback (defined by the control).
22783     */
22784     callback: function (name, args) {
22785         if (name && this.callbacks[name]) {
22786             this.callbacks[name].apply(this.control, args);
22787         }
22788     },
22789
22790     /**
22791     * Method: register
22792     * register an event on the map
22793     */
22794     register: function (name, method) {
22795         // TODO: deal with registerPriority in 3.0
22796         this.map.events.registerPriority(name, this, method);
22797         this.map.events.registerPriority(name, this, this.setEvent);
22798     },
22799
22800     /**
22801     * Method: unregister
22802     * unregister an event from the map
22803     */
22804     unregister: function (name, method) {
22805         this.map.events.unregister(name, this, method);   
22806         this.map.events.unregister(name, this, this.setEvent);
22807     },
22808     
22809     /**
22810      * Method: setEvent
22811      * With each registered browser event, the handler sets its own evt
22812      *     property.  This property can be accessed by controls if needed
22813      *     to get more information about the event that the handler is
22814      *     processing.
22815      *
22816      * This allows modifier keys on the event to be checked (alt, shift, ctrl,
22817      *     and meta cannot be checked with the keyboard handler).  For a
22818      *     control to determine which modifier keys are associated with the
22819      *     event that a handler is currently processing, it should access
22820      *     (code)handler.evt.altKey || handler.evt.shiftKey ||
22821      *     handler.evt.ctrlKey || handler.evt.metaKey(end).
22822      *
22823      * Parameters:
22824      * evt - {Event} The browser event.
22825      */
22826     setEvent: function(evt) {
22827         this.evt = evt;
22828         return true;
22829     },
22830
22831     /**
22832      * Method: destroy
22833      * Deconstruct the handler.
22834      */
22835     destroy: function () {
22836         // unregister event listeners
22837         this.deactivate();
22838         // eliminate circular references
22839         this.control = this.map = null;        
22840     },
22841
22842     CLASS_NAME: "OpenLayers.Handler"
22843 });
22844
22845 /**
22846  * Constant: OpenLayers.Handler.MOD_NONE
22847  * If set as the <keyMask>, <checkModifiers> returns false if any key is down.
22848  */
22849 OpenLayers.Handler.MOD_NONE  = 0;
22850
22851 /**
22852  * Constant: OpenLayers.Handler.MOD_SHIFT
22853  * If set as the <keyMask>, <checkModifiers> returns false if Shift is down.
22854  */
22855 OpenLayers.Handler.MOD_SHIFT = 1;
22856
22857 /**
22858  * Constant: OpenLayers.Handler.MOD_CTRL
22859  * If set as the <keyMask>, <checkModifiers> returns false if Ctrl is down.
22860  */
22861 OpenLayers.Handler.MOD_CTRL  = 2;
22862
22863 /**
22864  * Constant: OpenLayers.Handler.MOD_ALT
22865  * If set as the <keyMask>, <checkModifiers> returns false if Alt is down.
22866  */
22867 OpenLayers.Handler.MOD_ALT   = 4;
22868
22869 /**
22870  * Constant: OpenLayers.Handler.MOD_META
22871  * If set as the <keyMask>, <checkModifiers> returns false if Cmd is down.
22872  */
22873 OpenLayers.Handler.MOD_META  = 8;
22874
22875
22876 /* ======================================================================
22877     OpenLayers/Handler/Drag.js
22878    ====================================================================== */
22879
22880 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
22881  * full list of contributors). Published under the 2-clause BSD license.
22882  * See license.txt in the OpenLayers distribution or repository for the
22883  * full text of the license. */
22884
22885 /**
22886  * @requires OpenLayers/Handler.js
22887  */
22888
22889 /**
22890  * Class: OpenLayers.Handler.Drag
22891  * The drag handler is used to deal with sequences of browser events related
22892  *     to dragging.  The handler is used by controls that want to know when
22893  *     a drag sequence begins, when a drag is happening, and when it has
22894  *     finished.
22895  *
22896  * Controls that use the drag handler typically construct it with callbacks
22897  *     for 'down', 'move', and 'done'.  Callbacks for these keys are called
22898  *     when the drag begins, with each move, and when the drag is done.  In
22899  *     addition, controls can have callbacks keyed to 'up' and 'out' if they
22900  *     care to differentiate between the types of events that correspond with
22901  *     the end of a drag sequence.  If no drag actually occurs (no mouse move)
22902  *     the 'down' and 'up' callbacks will be called, but not the 'done'
22903  *     callback.
22904  *
22905  * Create a new drag handler with the <OpenLayers.Handler.Drag> constructor.
22906  *
22907  * Inherits from:
22908  *  - <OpenLayers.Handler>
22909  */
22910 OpenLayers.Handler.Drag = OpenLayers.Class(OpenLayers.Handler, {
22911   
22912     /** 
22913      * Property: started
22914      * {Boolean} When a mousedown or touchstart event is received, we want to
22915      * record it, but not set 'dragging' until the mouse moves after starting.
22916      */
22917     started: false,
22918
22919     /**
22920      * Property: stopDown
22921      * {Boolean} Stop propagation of mousedown events from getting to listeners
22922      *     on the same element.  Default is true.
22923      */
22924     stopDown: true,
22925
22926     /** 
22927      * Property: dragging 
22928      * {Boolean} 
22929      */
22930     dragging: false,
22931
22932     /** 
22933      * Property: last
22934      * {<OpenLayers.Pixel>} The last pixel location of the drag.
22935      */
22936     last: null,
22937
22938     /** 
22939      * Property: start
22940      * {<OpenLayers.Pixel>} The first pixel location of the drag.
22941      */
22942     start: null,
22943
22944     /**
22945      * Property: lastMoveEvt
22946      * {Object} The last mousemove event that occurred. Used to
22947      *     position the map correctly when our "delay drag"
22948      *     timeout expired.
22949      */
22950     lastMoveEvt: null,
22951
22952     /**
22953      * Property: oldOnselectstart
22954      * {Function}
22955      */
22956     oldOnselectstart: null,
22957     
22958     /**
22959      * Property: interval
22960      * {Integer} In order to increase performance, an interval (in 
22961      *     milliseconds) can be set to reduce the number of drag events 
22962      *     called. If set, a new drag event will not be set until the 
22963      *     interval has passed. 
22964      *     Defaults to 0, meaning no interval. 
22965      */
22966     interval: 0,
22967     
22968     /**
22969      * Property: timeoutId
22970      * {String} The id of the timeout used for the mousedown interval.
22971      *     This is "private", and should be left alone.
22972      */
22973     timeoutId: null,
22974     
22975     /**
22976      * APIProperty: documentDrag
22977      * {Boolean} If set to true, the handler will also handle mouse moves when
22978      *     the cursor has moved out of the map viewport. Default is false.
22979      */
22980     documentDrag: false,
22981     
22982     /**
22983      * Property: documentEvents
22984      * {Boolean} Are we currently observing document events?
22985      */
22986     documentEvents: null,
22987
22988     /**
22989      * Constructor: OpenLayers.Handler.Drag
22990      * Returns OpenLayers.Handler.Drag
22991      * 
22992      * Parameters:
22993      * control - {<OpenLayers.Control>} The control that is making use of
22994      *     this handler.  If a handler is being used without a control, the
22995      *     handlers setMap method must be overridden to deal properly with
22996      *     the map.
22997      * callbacks - {Object} An object containing a single function to be
22998      *     called when the drag operation is finished. The callback should
22999      *     expect to receive a single argument, the pixel location of the event.
23000      *     Callbacks for 'move' and 'done' are supported. You can also speficy
23001      *     callbacks for 'down', 'up', and 'out' to respond to those events.
23002      * options - {Object} 
23003      */
23004     initialize: function(control, callbacks, options) {
23005         OpenLayers.Handler.prototype.initialize.apply(this, arguments);
23006         
23007         if (this.documentDrag === true) {
23008             var me = this;
23009             this._docMove = function(evt) {
23010                 me.mousemove({
23011                     xy: {x: evt.clientX, y: evt.clientY},
23012                     element: document
23013                 });
23014             };
23015             this._docUp = function(evt) {
23016                 me.mouseup({xy: {x: evt.clientX, y: evt.clientY}});
23017             };
23018         }
23019     },
23020
23021     
23022     /**
23023      * Method: dragstart
23024      * This private method is factorized from mousedown and touchstart methods
23025      *
23026      * Parameters:
23027      * evt - {Event} The event
23028      *
23029      * Returns:
23030      * {Boolean} Let the event propagate.
23031      */
23032     dragstart: function (evt) {
23033         var propagate = true;
23034         this.dragging = false;
23035         if (this.checkModifiers(evt) &&
23036                this._pointerId == evt.pointerId &&
23037                (OpenLayers.Event.isLeftClick(evt) ||
23038                 OpenLayers.Event.isSingleTouch(evt))) {
23039             this.started = true;
23040             this.start = evt.xy;
23041             this.last = evt.xy;
23042             OpenLayers.Element.addClass(
23043                 this.map.viewPortDiv, "olDragDown"
23044             );
23045             this.down(evt);
23046             this.callback("down", [evt.xy]);
23047
23048             // prevent document dragging
23049             OpenLayers.Event.preventDefault(evt);
23050
23051             if(!this.oldOnselectstart) {
23052                 this.oldOnselectstart = document.onselectstart ?
23053                     document.onselectstart : OpenLayers.Function.True;
23054             }
23055             document.onselectstart = OpenLayers.Function.False;
23056
23057             propagate = !this.stopDown;
23058         } else {
23059             delete this._pointerId;
23060             this.started = false;
23061             this.start = null;
23062             this.last = null;
23063         }
23064         return propagate;
23065     },
23066
23067     /**
23068      * Method: dragmove
23069      * This private method is factorized from mousemove and touchmove methods
23070      *
23071      * Parameters:
23072      * evt - {Event} The event
23073      *
23074      * Returns:
23075      * {Boolean} Let the event propagate.
23076      */
23077     dragmove: function (evt) {
23078         this.lastMoveEvt = evt;
23079         if (this.started && this._pointerId == evt.pointerId &&
23080             !this.timeoutId && (evt.xy.x != this.last.x ||
23081                                 evt.xy.y != this.last.y)) {
23082             if(this.documentDrag === true && this.documentEvents) {
23083                 if(evt.element === document) {
23084                     this.adjustXY(evt);
23085                     // do setEvent manually because the documentEvents are not
23086                     // registered with the map
23087                     this.setEvent(evt);
23088                 } else {
23089                     this.removeDocumentEvents();
23090                 }
23091             }
23092             if (this.interval > 0) {
23093                 this.timeoutId = setTimeout(
23094                     OpenLayers.Function.bind(this.removeTimeout, this),
23095                     this.interval);
23096             }
23097             this.dragging = true;
23098
23099             this.move(evt);
23100             this.callback("move", [evt.xy]);
23101             if(!this.oldOnselectstart) {
23102                 this.oldOnselectstart = document.onselectstart;
23103                 document.onselectstart = OpenLayers.Function.False;
23104             }
23105             this.last = evt.xy;
23106         }
23107         return true;
23108     },
23109
23110     /**
23111      * Method: dragend
23112      * This private method is factorized from mouseup and touchend methods
23113      *
23114      * Parameters:
23115      * evt - {Event} The event
23116      *
23117      * Returns:
23118      * {Boolean} Let the event propagate.
23119      */
23120     dragend: function (evt) {
23121         if (this.started && this._pointerId == evt.pointerId) {
23122             if(this.documentDrag === true && this.documentEvents) {
23123                 this.adjustXY(evt);
23124                 this.removeDocumentEvents();
23125             }
23126             var dragged = (this.start != this.last);
23127             this.started = false;
23128             this.dragging = false;
23129             delete this._pointerId;
23130             OpenLayers.Element.removeClass(
23131                 this.map.viewPortDiv, "olDragDown"
23132             );
23133             this.up(evt);
23134             this.callback("up", [evt.xy]);
23135             if(dragged) {
23136                 this.callback("done", [evt.xy]);
23137             }
23138             document.onselectstart = this.oldOnselectstart;
23139         }
23140         return true;
23141     },
23142
23143     /**
23144      * The four methods below (down, move, up, and out) are used by subclasses
23145      *     to do their own processing related to these mouse events.
23146      */
23147
23148     /**
23149      * Method: down
23150      * This method is called during the handling of the mouse down event.
23151      *     Subclasses can do their own processing here.
23152      *
23153      * Parameters:
23154      * evt - {Event} The mouse down event
23155      */
23156     down: function(evt) {
23157     },
23158
23159     /**
23160      * Method: move
23161      * This method is called during the handling of the mouse move event.
23162      *     Subclasses can do their own processing here.
23163      *
23164      * Parameters:
23165      * evt - {Event} The mouse move event
23166      *
23167      */
23168     move: function(evt) {
23169     },
23170
23171     /**
23172      * Method: up
23173      * This method is called during the handling of the mouse up event.
23174      *     Subclasses can do their own processing here.
23175      *
23176      * Parameters:
23177      * evt - {Event} The mouse up event
23178      */
23179     up: function(evt) {
23180     },
23181
23182     /**
23183      * Method: out
23184      * This method is called during the handling of the mouse out event.
23185      *     Subclasses can do their own processing here.
23186      *
23187      * Parameters:
23188      * evt - {Event} The mouse out event
23189      */
23190     out: function(evt) {
23191     },
23192
23193     /**
23194      * The methods below are part of the magic of event handling.  Because
23195      *     they are named like browser events, they are registered as listeners
23196      *     for the events they represent.
23197      */
23198
23199     /**
23200      * Method: mousedown
23201      * Handle mousedown events
23202      *
23203      * Parameters:
23204      * evt - {Event}
23205      *
23206      * Returns:
23207      * {Boolean} Let the event propagate.
23208      */
23209     mousedown: function(evt) {
23210         return this.dragstart(evt);
23211     },
23212
23213     /**
23214      * Method: touchstart
23215      * Handle touchstart events
23216      *
23217      * Parameters:
23218      * evt - {Event}
23219      *
23220      * Returns:
23221      * {Boolean} Let the event propagate.
23222      */
23223     touchstart: function(evt) {
23224         this.startTouch();
23225         // only allow the first pointer event to be monitored by noting its pointerId
23226         // which is unique in the pointer model (and undefined in the touch model)
23227         if (!("_pointerId" in this)) {
23228             this._pointerId = evt.pointerId;
23229         }
23230         return this.dragstart(evt);
23231     },
23232
23233     /**
23234      * Method: mousemove
23235      * Handle mousemove events
23236      *
23237      * Parameters:
23238      * evt - {Event}
23239      *
23240      * Returns:
23241      * {Boolean} Let the event propagate.
23242      */
23243     mousemove: function(evt) {
23244         return this.dragmove(evt);
23245     },
23246
23247     /**
23248      * Method: touchmove
23249      * Handle touchmove events
23250      *
23251      * Parameters:
23252      * evt - {Event}
23253      *
23254      * Returns:
23255      * {Boolean} Let the event propagate.
23256      */
23257     touchmove: function(evt) {
23258         return this.dragmove(evt);
23259     },
23260
23261     /**
23262      * Method: removeTimeout
23263      * Private. Called by mousemove() to remove the drag timeout.
23264      */
23265     removeTimeout: function() {
23266         this.timeoutId = null;
23267         // if timeout expires while we're still dragging (mouseup
23268         // hasn't occurred) then call mousemove to move to the
23269         // correct position
23270         if(this.dragging) {
23271             this.mousemove(this.lastMoveEvt);
23272         }
23273     },
23274
23275     /**
23276      * Method: mouseup
23277      * Handle mouseup events
23278      *
23279      * Parameters:
23280      * evt - {Event}
23281      *
23282      * Returns:
23283      * {Boolean} Let the event propagate.
23284      */
23285     mouseup: function(evt) {
23286         return this.dragend(evt);
23287     },
23288
23289     /**
23290      * Method: touchend
23291      * Handle touchend events
23292      *
23293      * Parameters:
23294      * evt - {Event}
23295      *
23296      * Returns:
23297      * {Boolean} Let the event propagate.
23298      */
23299     touchend: function(evt) {
23300         // override evt.xy with last position since touchend does not have
23301         // any touch position
23302         evt.xy = this.last;
23303         return this.dragend(evt);
23304     },
23305
23306     /**
23307      * Method: mouseout
23308      * Handle mouseout events
23309      *
23310      * Parameters:
23311      * evt - {Event}
23312      *
23313      * Returns:
23314      * {Boolean} Let the event propagate.
23315      */
23316     mouseout: function (evt) {
23317         if (this.started && OpenLayers.Util.mouseLeft(evt, this.map.viewPortDiv)) {
23318             if(this.documentDrag === true) {
23319                 this.addDocumentEvents();
23320             } else {
23321                 var dragged = (this.start != this.last);
23322                 this.started = false; 
23323                 this.dragging = false;
23324                 OpenLayers.Element.removeClass(
23325                     this.map.viewPortDiv, "olDragDown"
23326                 );
23327                 this.out(evt);
23328                 this.callback("out", []);
23329                 if(dragged) {
23330                     this.callback("done", [evt.xy]);
23331                 }
23332                 if(document.onselectstart) {
23333                     document.onselectstart = this.oldOnselectstart;
23334                 }
23335             }
23336         }
23337         return true;
23338     },
23339
23340     /**
23341      * Method: click
23342      * The drag handler captures the click event.  If something else registers
23343      *     for clicks on the same element, its listener will not be called 
23344      *     after a drag.
23345      * 
23346      * Parameters: 
23347      * evt - {Event} 
23348      * 
23349      * Returns:
23350      * {Boolean} Let the event propagate.
23351      */
23352     click: function (evt) {
23353         // let the click event propagate only if the mouse moved
23354         return (this.start == this.last);
23355     },
23356
23357     /**
23358      * Method: activate
23359      * Activate the handler.
23360      * 
23361      * Returns:
23362      * {Boolean} The handler was successfully activated.
23363      */
23364     activate: function() {
23365         var activated = false;
23366         if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
23367             this.dragging = false;
23368             activated = true;
23369         }
23370         return activated;
23371     },
23372
23373     /**
23374      * Method: deactivate 
23375      * Deactivate the handler.
23376      * 
23377      * Returns:
23378      * {Boolean} The handler was successfully deactivated.
23379      */
23380     deactivate: function() {
23381         var deactivated = false;
23382         if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
23383             this.started = false;
23384             this.dragging = false;
23385             this.start = null;
23386             this.last = null;
23387             deactivated = true;
23388             OpenLayers.Element.removeClass(
23389                 this.map.viewPortDiv, "olDragDown"
23390             );
23391         }
23392         return deactivated;
23393     },
23394     
23395     /**
23396      * Method: adjustXY
23397      * Converts event coordinates that are relative to the document body to
23398      * ones that are relative to the map viewport. The latter is the default in
23399      * OpenLayers.
23400      * 
23401      * Parameters:
23402      * evt - {Object}
23403      */
23404     adjustXY: function(evt) {
23405         var pos = OpenLayers.Util.pagePosition(this.map.viewPortDiv);
23406         evt.xy.x -= pos[0];
23407         evt.xy.y -= pos[1];
23408     },
23409     
23410     /**
23411      * Method: addDocumentEvents
23412      * Start observing document events when documentDrag is true and the mouse
23413      * cursor leaves the map viewport while dragging.
23414      */
23415     addDocumentEvents: function() {
23416         OpenLayers.Element.addClass(document.body, "olDragDown");
23417         this.documentEvents = true;
23418         OpenLayers.Event.observe(document, "mousemove", this._docMove);
23419         OpenLayers.Event.observe(document, "mouseup", this._docUp);
23420     },
23421     
23422     /**
23423      * Method: removeDocumentEvents
23424      * Stops observing document events when documentDrag is true and the mouse
23425      * cursor re-enters the map viewport while dragging.
23426      */
23427     removeDocumentEvents: function() {
23428         OpenLayers.Element.removeClass(document.body, "olDragDown");
23429         this.documentEvents = false;
23430         OpenLayers.Event.stopObserving(document, "mousemove", this._docMove);
23431         OpenLayers.Event.stopObserving(document, "mouseup", this._docUp);
23432     },
23433
23434     CLASS_NAME: "OpenLayers.Handler.Drag"
23435 });
23436 /* ======================================================================
23437     OpenLayers/Handler/Box.js
23438    ====================================================================== */
23439
23440 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
23441  * full list of contributors). Published under the 2-clause BSD license.
23442  * See license.txt in the OpenLayers distribution or repository for the
23443  * full text of the license. */
23444
23445 /**
23446  * @requires OpenLayers/Handler.js
23447  * @requires OpenLayers/Handler/Drag.js
23448  */
23449
23450 /**
23451  * Class: OpenLayers.Handler.Box
23452  * Handler for dragging a rectangle across the map.  Box is displayed 
23453  * on mouse down, moves on mouse move, and is finished on mouse up.
23454  *
23455  * Inherits from:
23456  *  - <OpenLayers.Handler> 
23457  */
23458 OpenLayers.Handler.Box = OpenLayers.Class(OpenLayers.Handler, {
23459
23460     /** 
23461      * Property: dragHandler 
23462      * {<OpenLayers.Handler.Drag>} 
23463      */
23464     dragHandler: null,
23465
23466     /**
23467      * APIProperty: boxDivClassName
23468      * {String} The CSS class to use for drawing the box. Default is
23469      *     olHandlerBoxZoomBox
23470      */
23471     boxDivClassName: 'olHandlerBoxZoomBox',
23472     
23473     /**
23474      * Property: boxOffsets
23475      * {Object} Caches box offsets from css. This is used by the getBoxOffsets
23476      * method.
23477      */
23478     boxOffsets: null,
23479
23480     /**
23481      * Constructor: OpenLayers.Handler.Box
23482      *
23483      * Parameters:
23484      * control - {<OpenLayers.Control>} 
23485      * callbacks - {Object} An object with a properties whose values are
23486      *     functions.  Various callbacks described below.
23487      * options - {Object} 
23488      *
23489      * Named callbacks:
23490      * start - Called when the box drag operation starts.
23491      * done - Called when the box drag operation is finished.
23492      *     The callback should expect to receive a single argument, the box 
23493      *     bounds or a pixel. If the box dragging didn't span more than a 5 
23494      *     pixel distance, a pixel will be returned instead of a bounds object.
23495      */
23496     initialize: function(control, callbacks, options) {
23497         OpenLayers.Handler.prototype.initialize.apply(this, arguments);
23498         this.dragHandler = new OpenLayers.Handler.Drag(
23499             this, 
23500             {
23501                 down: this.startBox, 
23502                 move: this.moveBox, 
23503                 out: this.removeBox,
23504                 up: this.endBox
23505             }, 
23506             {keyMask: this.keyMask}
23507         );
23508     },
23509
23510     /**
23511      * Method: destroy
23512      */
23513     destroy: function() {
23514         OpenLayers.Handler.prototype.destroy.apply(this, arguments);
23515         if (this.dragHandler) {
23516             this.dragHandler.destroy();
23517             this.dragHandler = null;
23518         }            
23519     },
23520
23521     /**
23522      * Method: setMap
23523      */
23524     setMap: function (map) {
23525         OpenLayers.Handler.prototype.setMap.apply(this, arguments);
23526         if (this.dragHandler) {
23527             this.dragHandler.setMap(map);
23528         }
23529     },
23530
23531     /**
23532     * Method: startBox
23533     *
23534     * Parameters:
23535     * xy - {<OpenLayers.Pixel>}
23536     */
23537     startBox: function (xy) {
23538         this.callback("start", []);
23539         this.zoomBox = OpenLayers.Util.createDiv('zoomBox', {
23540             x: -9999, y: -9999
23541         });
23542         this.zoomBox.className = this.boxDivClassName;                                         
23543         this.zoomBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1;
23544         
23545         this.map.viewPortDiv.appendChild(this.zoomBox);
23546         
23547         OpenLayers.Element.addClass(
23548             this.map.viewPortDiv, "olDrawBox"
23549         );
23550     },
23551
23552     /**
23553     * Method: moveBox
23554     */
23555     moveBox: function (xy) {
23556         var startX = this.dragHandler.start.x;
23557         var startY = this.dragHandler.start.y;
23558         var deltaX = Math.abs(startX - xy.x);
23559         var deltaY = Math.abs(startY - xy.y);
23560
23561         var offset = this.getBoxOffsets();
23562         this.zoomBox.style.width = (deltaX + offset.width + 1) + "px";
23563         this.zoomBox.style.height = (deltaY + offset.height + 1) + "px";
23564         this.zoomBox.style.left = (xy.x < startX ?
23565             startX - deltaX - offset.left : startX - offset.left) + "px";
23566         this.zoomBox.style.top = (xy.y < startY ?
23567             startY - deltaY - offset.top : startY - offset.top) + "px";
23568     },
23569
23570     /**
23571     * Method: endBox
23572     */
23573     endBox: function(end) {
23574         var result;
23575         if (Math.abs(this.dragHandler.start.x - end.x) > 5 ||    
23576             Math.abs(this.dragHandler.start.y - end.y) > 5) {   
23577             var start = this.dragHandler.start;
23578             var top = Math.min(start.y, end.y);
23579             var bottom = Math.max(start.y, end.y);
23580             var left = Math.min(start.x, end.x);
23581             var right = Math.max(start.x, end.x);
23582             result = new OpenLayers.Bounds(left, bottom, right, top);
23583         } else {
23584             result = this.dragHandler.start.clone(); // i.e. OL.Pixel
23585         } 
23586         this.removeBox();
23587
23588         this.callback("done", [result]);
23589     },
23590
23591     /**
23592      * Method: removeBox
23593      * Remove the zoombox from the screen and nullify our reference to it.
23594      */
23595     removeBox: function() {
23596         this.map.viewPortDiv.removeChild(this.zoomBox);
23597         this.zoomBox = null;
23598         this.boxOffsets = null;
23599         OpenLayers.Element.removeClass(
23600             this.map.viewPortDiv, "olDrawBox"
23601         );
23602
23603     },
23604
23605     /**
23606      * Method: activate
23607      */
23608     activate: function () {
23609         if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
23610             this.dragHandler.activate();
23611             return true;
23612         } else {
23613             return false;
23614         }
23615     },
23616
23617     /**
23618      * Method: deactivate
23619      */
23620     deactivate: function () {
23621         if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
23622             if (this.dragHandler.deactivate()) {
23623                 if (this.zoomBox) {
23624                     this.removeBox();
23625                 }
23626             }
23627             return true;
23628         } else {
23629             return false;
23630         }
23631     },
23632     
23633     /**
23634      * Method: getBoxOffsets
23635      * Determines border offsets for a box, according to the box model.
23636      * 
23637      * Returns:
23638      * {Object} an object with the following offsets:
23639      *     - left
23640      *     - right
23641      *     - top
23642      *     - bottom
23643      *     - width
23644      *     - height
23645      */
23646     getBoxOffsets: function() {
23647         if (!this.boxOffsets) {
23648             // Determine the box model. If the testDiv's clientWidth is 3, then
23649             // the borders are outside and we are dealing with the w3c box
23650             // model. Otherwise, the browser uses the traditional box model and
23651             // the borders are inside the box bounds, leaving us with a
23652             // clientWidth of 1.
23653             var testDiv = document.createElement("div");
23654             //testDiv.style.visibility = "hidden";
23655             testDiv.style.position = "absolute";
23656             testDiv.style.border = "1px solid black";
23657             testDiv.style.width = "3px";
23658             document.body.appendChild(testDiv);
23659             var w3cBoxModel = testDiv.clientWidth == 3;
23660             document.body.removeChild(testDiv);
23661             
23662             var left = parseInt(OpenLayers.Element.getStyle(this.zoomBox,
23663                 "border-left-width"));
23664             var right = parseInt(OpenLayers.Element.getStyle(
23665                 this.zoomBox, "border-right-width"));
23666             var top = parseInt(OpenLayers.Element.getStyle(this.zoomBox,
23667                 "border-top-width"));
23668             var bottom = parseInt(OpenLayers.Element.getStyle(
23669                 this.zoomBox, "border-bottom-width"));
23670             this.boxOffsets = {
23671                 left: left,
23672                 right: right,
23673                 top: top,
23674                 bottom: bottom,
23675                 width: w3cBoxModel === false ? left + right : 0,
23676                 height: w3cBoxModel === false ? top + bottom : 0
23677             };
23678         }
23679         return this.boxOffsets;
23680     },
23681   
23682     CLASS_NAME: "OpenLayers.Handler.Box"
23683 });
23684 /* ======================================================================
23685     OpenLayers/Control/ZoomBox.js
23686    ====================================================================== */
23687
23688 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
23689  * full list of contributors). Published under the 2-clause BSD license.
23690  * See license.txt in the OpenLayers distribution or repository for the
23691  * full text of the license. */
23692
23693 /**
23694  * @requires OpenLayers/Control.js
23695  * @requires OpenLayers/Handler/Box.js
23696  */
23697
23698 /**
23699  * Class: OpenLayers.Control.ZoomBox
23700  * The ZoomBox control enables zooming directly to a given extent, by drawing 
23701  * a box on the map. The box is drawn by holding down shift, whilst dragging 
23702  * the mouse.
23703  *
23704  * Inherits from:
23705  *  - <OpenLayers.Control>
23706  */
23707 OpenLayers.Control.ZoomBox = OpenLayers.Class(OpenLayers.Control, {
23708     /**
23709      * Property: type
23710      * {OpenLayers.Control.TYPE}
23711      */
23712     type: OpenLayers.Control.TYPE_TOOL,
23713
23714     /**
23715      * Property: out
23716      * {Boolean} Should the control be used for zooming out?
23717      */
23718     out: false,
23719
23720     /**
23721      * APIProperty: keyMask
23722      * {Integer} Zoom only occurs if the keyMask matches the combination of 
23723      *     keys down. Use bitwise operators and one or more of the
23724      *     <OpenLayers.Handler> constants to construct a keyMask. Leave null if 
23725      *     not used mask. Default is null.
23726      */
23727     keyMask: null,
23728
23729     /**
23730      * APIProperty: alwaysZoom
23731      * {Boolean} Always zoom in/out when box drawn, even if the zoom level does
23732      * not change.
23733      */
23734     alwaysZoom: false,
23735     
23736     /**
23737      * APIProperty: zoomOnClick
23738      * {Boolean} Should we zoom when no box was dragged, i.e. the user only
23739      * clicked? Default is true.
23740      */
23741     zoomOnClick: true,
23742
23743     /**
23744      * Method: draw
23745      */    
23746     draw: function() {
23747         this.handler = new OpenLayers.Handler.Box( this,
23748                             {done: this.zoomBox}, {keyMask: this.keyMask} );
23749     },
23750
23751     /**
23752      * Method: zoomBox
23753      *
23754      * Parameters:
23755      * position - {<OpenLayers.Bounds>} or {<OpenLayers.Pixel>}
23756      */
23757     zoomBox: function (position) {
23758         if (position instanceof OpenLayers.Bounds) {
23759             var bounds,
23760                 targetCenterPx = position.getCenterPixel();
23761             if (!this.out) {
23762                 var minXY = this.map.getLonLatFromPixel({
23763                     x: position.left,
23764                     y: position.bottom
23765                 });
23766                 var maxXY = this.map.getLonLatFromPixel({
23767                     x: position.right,
23768                     y: position.top
23769                 });
23770                 bounds = new OpenLayers.Bounds(minXY.lon, minXY.lat,
23771                                                maxXY.lon, maxXY.lat);
23772             } else {
23773                 var pixWidth = position.right - position.left;
23774                 var pixHeight = position.bottom - position.top;
23775                 var zoomFactor = Math.min((this.map.size.h / pixHeight),
23776                     (this.map.size.w / pixWidth));
23777                 var extent = this.map.getExtent();
23778                 var center = this.map.getLonLatFromPixel(targetCenterPx);
23779                 var xmin = center.lon - (extent.getWidth()/2)*zoomFactor;
23780                 var xmax = center.lon + (extent.getWidth()/2)*zoomFactor;
23781                 var ymin = center.lat - (extent.getHeight()/2)*zoomFactor;
23782                 var ymax = center.lat + (extent.getHeight()/2)*zoomFactor;
23783                 bounds = new OpenLayers.Bounds(xmin, ymin, xmax, ymax);
23784             }
23785             // always zoom in/out 
23786             var lastZoom = this.map.getZoom(),
23787                 size = this.map.getSize(),
23788                 centerPx = {x: size.w / 2, y: size.h / 2},
23789                 zoom = this.map.getZoomForExtent(bounds),
23790                 oldRes = this.map.getResolution(),
23791                 newRes = this.map.getResolutionForZoom(zoom);
23792             if (oldRes == newRes) {
23793                 this.map.setCenter(this.map.getLonLatFromPixel(targetCenterPx));
23794             } else {
23795               var zoomOriginPx = {
23796                     x: (oldRes * targetCenterPx.x - newRes * centerPx.x) /
23797                         (oldRes - newRes),
23798                     y: (oldRes * targetCenterPx.y - newRes * centerPx.y) /
23799                         (oldRes - newRes)
23800                 };
23801                 this.map.zoomTo(zoom, zoomOriginPx);
23802             }
23803             if (lastZoom == this.map.getZoom() && this.alwaysZoom == true){ 
23804                 this.map.zoomTo(lastZoom + (this.out ? -1 : 1)); 
23805             }
23806         } else if (this.zoomOnClick) { // it's a pixel
23807             if (!this.out) {
23808                 this.map.zoomTo(this.map.getZoom() + 1, position);
23809             } else {
23810                 this.map.zoomTo(this.map.getZoom() - 1, position);
23811             }
23812         }
23813     },
23814
23815     CLASS_NAME: "OpenLayers.Control.ZoomBox"
23816 });
23817 /* ======================================================================
23818     OpenLayers/Control/DragPan.js
23819    ====================================================================== */
23820
23821 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
23822  * full list of contributors). Published under the 2-clause BSD license.
23823  * See license.txt in the OpenLayers distribution or repository for the
23824  * full text of the license. */
23825
23826 /**
23827  * @requires OpenLayers/Control.js
23828  * @requires OpenLayers/Handler/Drag.js
23829  */
23830
23831 /**
23832  * Class: OpenLayers.Control.DragPan
23833  * The DragPan control pans the map with a drag of the mouse.
23834  *
23835  * Inherits from:
23836  *  - <OpenLayers.Control>
23837  */
23838 OpenLayers.Control.DragPan = OpenLayers.Class(OpenLayers.Control, {
23839
23840     /** 
23841      * Property: type
23842      * {OpenLayers.Control.TYPES}
23843      */
23844     type: OpenLayers.Control.TYPE_TOOL,
23845     
23846     /**
23847      * Property: panned
23848      * {Boolean} The map moved.
23849      */
23850     panned: false,
23851     
23852     /**
23853      * Property: interval
23854      * {Integer} The number of milliseconds that should ellapse before
23855      *     panning the map again. Defaults to 0 milliseconds, which means that
23856      *     no separate cycle is used for panning. In most cases you won't want
23857      *     to change this value. For slow machines/devices larger values can be
23858      *     tried out.
23859      */
23860     interval: 0,
23861     
23862     /**
23863      * APIProperty: documentDrag
23864      * {Boolean} If set to true, mouse dragging will continue even if the
23865      *     mouse cursor leaves the map viewport. Default is false.
23866      */
23867     documentDrag: false,
23868
23869     /**
23870      * Property: kinetic
23871      * {<OpenLayers.Kinetic>} The OpenLayers.Kinetic object.
23872      */
23873     kinetic: null,
23874
23875     /**
23876      * APIProperty: enableKinetic
23877      * {Boolean} Set this option to enable "kinetic dragging". Can be
23878      *     set to true or to an object. If set to an object this
23879      *     object will be passed to the {<OpenLayers.Kinetic>}
23880      *     constructor. Defaults to true.
23881      *     To get kinetic dragging, ensure that OpenLayers/Kinetic.js is
23882      *     included in your build config.
23883      */
23884     enableKinetic: true,
23885
23886     /**
23887      * APIProperty: kineticInterval
23888      * {Integer} Interval in milliseconds between 2 steps in the "kinetic
23889      *     scrolling". Applies only if enableKinetic is set. Defaults
23890      *     to 10 milliseconds.
23891      */
23892     kineticInterval: 10,
23893
23894
23895     /**
23896      * Method: draw
23897      * Creates a Drag handler, using <panMap> and
23898      * <panMapDone> as callbacks.
23899      */    
23900     draw: function() {
23901         if (this.enableKinetic && OpenLayers.Kinetic) {
23902             var config = {interval: this.kineticInterval};
23903             if(typeof this.enableKinetic === "object") {
23904                 config = OpenLayers.Util.extend(config, this.enableKinetic);
23905             }
23906             this.kinetic = new OpenLayers.Kinetic(config);
23907         }
23908         this.handler = new OpenLayers.Handler.Drag(this, {
23909                 "move": this.panMap,
23910                 "done": this.panMapDone,
23911                 "down": this.panMapStart
23912             }, {
23913                 interval: this.interval,
23914                 documentDrag: this.documentDrag
23915             }
23916         );
23917     },
23918
23919     /**
23920      * Method: panMapStart
23921      */
23922     panMapStart: function() {
23923         if(this.kinetic) {
23924             this.kinetic.begin();
23925         }
23926     },
23927
23928     /**
23929     * Method: panMap
23930     *
23931     * Parameters:
23932     * xy - {<OpenLayers.Pixel>} Pixel of the mouse position
23933     */
23934     panMap: function(xy) {
23935         if(this.kinetic) {
23936             this.kinetic.update(xy);
23937         }
23938         this.panned = true;
23939         this.map.pan(
23940             this.handler.last.x - xy.x,
23941             this.handler.last.y - xy.y,
23942             {dragging: true, animate: false}
23943         );
23944     },
23945     
23946     /**
23947      * Method: panMapDone
23948      * Finish the panning operation.  Only call setCenter (through <panMap>)
23949      *     if the map has actually been moved.
23950      *
23951      * Parameters:
23952      * xy - {<OpenLayers.Pixel>} Pixel of the mouse position
23953      */
23954     panMapDone: function(xy) {
23955         if(this.panned) {
23956             var res = null;
23957             if (this.kinetic) {
23958                 res = this.kinetic.end(xy);
23959             }
23960             this.map.pan(
23961                 this.handler.last.x - xy.x,
23962                 this.handler.last.y - xy.y,
23963                 {dragging: !!res, animate: false}
23964             );
23965             if (res) {
23966                 var self = this;
23967                 this.kinetic.move(res, function(x, y, end) {
23968                     self.map.pan(x, y, {dragging: !end, animate: false});
23969                 });
23970             }
23971             this.panned = false;
23972         }
23973     },
23974
23975     CLASS_NAME: "OpenLayers.Control.DragPan"
23976 });
23977 /* ======================================================================
23978     OpenLayers/Handler/MouseWheel.js
23979    ====================================================================== */
23980
23981 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
23982  * full list of contributors). Published under the 2-clause BSD license.
23983  * See license.txt in the OpenLayers distribution or repository for the
23984  * full text of the license. */
23985
23986 /**
23987  * @requires OpenLayers/Handler.js
23988  */
23989
23990 /**
23991  * Class: OpenLayers.Handler.MouseWheel
23992  * Handler for wheel up/down events.
23993  * 
23994  * Inherits from:
23995  *  - <OpenLayers.Handler>
23996  */
23997 OpenLayers.Handler.MouseWheel = OpenLayers.Class(OpenLayers.Handler, {
23998     /** 
23999      * Property: wheelListener 
24000      * {function} 
24001      */
24002     wheelListener: null,
24003
24004     /**
24005      * Property: interval
24006      * {Integer} In order to increase server performance, an interval (in 
24007      *     milliseconds) can be set to reduce the number of up/down events 
24008      *     called. If set, a new up/down event will not be set until the 
24009      *     interval has passed. 
24010      *     Defaults to 0, meaning no interval. 
24011      */
24012     interval: 0,
24013     
24014     /**
24015      * Property: maxDelta
24016      * {Integer} Maximum delta to collect before breaking from the current
24017      *    interval. In cumulative mode, this also limits the maximum delta
24018      *    returned from the handler. Default is Number.POSITIVE_INFINITY.
24019      */
24020     maxDelta: Number.POSITIVE_INFINITY,
24021     
24022     /**
24023      * Property: delta
24024      * {Integer} When interval is set, delta collects the mousewheel z-deltas
24025      *     of the events that occur within the interval.
24026      *      See also the cumulative option
24027      */
24028     delta: 0,
24029     
24030     /**
24031      * Property: cumulative
24032      * {Boolean} When interval is set: true to collect all the mousewheel 
24033      *     z-deltas, false to only record the delta direction (positive or
24034      *     negative)
24035      */
24036     cumulative: true,
24037     
24038     /**
24039      * Constructor: OpenLayers.Handler.MouseWheel
24040      *
24041      * Parameters:
24042      * control - {<OpenLayers.Control>} 
24043      * callbacks - {Object} An object containing a single function to be
24044      *                          called when the drag operation is finished.
24045      *                          The callback should expect to receive a single
24046      *                          argument, the point geometry.
24047      * options - {Object} 
24048      */
24049     initialize: function(control, callbacks, options) {
24050         OpenLayers.Handler.prototype.initialize.apply(this, arguments);
24051         this.wheelListener = OpenLayers.Function.bindAsEventListener(
24052             this.onWheelEvent, this
24053         );
24054     },
24055
24056     /**
24057      * Method: destroy
24058      */    
24059     destroy: function() {
24060         OpenLayers.Handler.prototype.destroy.apply(this, arguments);
24061         this.wheelListener = null;
24062     },
24063
24064     /**
24065      *  Mouse ScrollWheel code thanks to http://adomas.org/javascript-mouse-wheel/
24066      */
24067
24068     /** 
24069      * Method: onWheelEvent
24070      * Catch the wheel event and handle it xbrowserly
24071      * 
24072      * Parameters:
24073      * e - {Event} 
24074      */
24075     onWheelEvent: function(e){
24076         
24077         // make sure we have a map and check keyboard modifiers
24078         if (!this.map || !this.checkModifiers(e)) {
24079             return;
24080         }
24081         
24082         // Ride up the element's DOM hierarchy to determine if it or any of 
24083         //  its ancestors was: 
24084         //   * specifically marked as scrollable (CSS overflow property)
24085         //   * one of our layer divs or a div marked as scrollable
24086         //     ('olScrollable' CSS class)
24087         //   * the map div
24088         //
24089         var overScrollableDiv = false;
24090         var allowScroll = false;
24091         var overMapDiv = false;
24092         
24093         var elem = OpenLayers.Event.element(e);
24094         while((elem != null) && !overMapDiv && !overScrollableDiv) {
24095
24096             if (!overScrollableDiv) {
24097                 try {
24098                     var overflow;
24099                     if (elem.currentStyle) {
24100                         overflow = elem.currentStyle["overflow"];
24101                     } else {
24102                         var style = 
24103                             document.defaultView.getComputedStyle(elem, null);
24104                         overflow = style.getPropertyValue("overflow");
24105                     }
24106                     overScrollableDiv = ( overflow && 
24107                         (overflow == "auto") || (overflow == "scroll") );
24108                 } catch(err) {
24109                     //sometimes when scrolling in a popup, this causes 
24110                     // obscure browser error
24111                 }
24112             }
24113
24114             if (!allowScroll) {
24115                 allowScroll = OpenLayers.Element.hasClass(elem, 'olScrollable');
24116                 if (!allowScroll) {
24117                     for (var i = 0, len = this.map.layers.length; i < len; i++) {
24118                         // Are we in the layer div? Note that we have two cases
24119                         // here: one is to catch EventPane layers, which have a
24120                         // pane above the layer (layer.pane)
24121                         var layer = this.map.layers[i];
24122                         if (elem == layer.div || elem == layer.pane) {
24123                             allowScroll = true;
24124                             break;
24125                         }
24126                     }
24127                 }
24128             }
24129             overMapDiv = (elem == this.map.div);
24130
24131             elem = elem.parentNode;
24132         }
24133         
24134         // Logic below is the following:
24135         //
24136         // If we are over a scrollable div or not over the map div:
24137         //  * do nothing (let the browser handle scrolling)
24138         //
24139         //    otherwise 
24140         // 
24141         //    If we are over the layer div or a 'olScrollable' div:
24142         //     * zoom/in out
24143         //     then
24144         //     * kill event (so as not to also scroll the page after zooming)
24145         //
24146         //       otherwise
24147         //
24148         //       Kill the event (dont scroll the page if we wheel over the 
24149         //        layerswitcher or the pan/zoom control)
24150         //
24151         if (!overScrollableDiv && overMapDiv) {
24152             if (allowScroll) {
24153                 var delta = 0;
24154                 
24155                 if (e.wheelDelta) {
24156                     delta = e.wheelDelta;
24157                     if (delta % 160 === 0) {
24158                         // opera have steps of 160 instead of 120
24159                         delta = delta * 0.75;
24160                     }
24161                     delta = delta / 120;
24162                 } else if (e.detail) {
24163                     // detail in Firefox on OS X is 1/3 of Windows
24164                     // so force delta 1 / -1
24165                     delta = - (e.detail / Math.abs(e.detail));
24166                 }
24167                 this.delta += delta;
24168
24169                 window.clearTimeout(this._timeoutId);
24170                 if(this.interval && Math.abs(this.delta) < this.maxDelta) {
24171                     // store e because window.event might change during delay
24172                     var evt = OpenLayers.Util.extend({}, e);
24173                     this._timeoutId = window.setTimeout(
24174                         OpenLayers.Function.bind(function(){
24175                             this.wheelZoom(evt);
24176                         }, this),
24177                         this.interval
24178                     );
24179                 } else {
24180                     this.wheelZoom(e);
24181                 }
24182             }
24183             OpenLayers.Event.stop(e);
24184         }
24185     },
24186
24187     /**
24188      * Method: wheelZoom
24189      * Given the wheel event, we carry out the appropriate zooming in or out,
24190      *     based on the 'wheelDelta' or 'detail' property of the event.
24191      * 
24192      * Parameters:
24193      * e - {Event}
24194      */
24195     wheelZoom: function(e) {
24196         var delta = this.delta;
24197         this.delta = 0;
24198         
24199         if (delta) {
24200             e.xy = this.map.events.getMousePosition(e);
24201             if (delta < 0) {
24202                 this.callback("down",
24203                     [e, this.cumulative ? Math.max(-this.maxDelta, delta) : -1]);
24204             } else {
24205                 this.callback("up",
24206                     [e, this.cumulative ? Math.min(this.maxDelta, delta) : 1]);
24207             }
24208         }
24209     },
24210     
24211     /**
24212      * Method: activate 
24213      */
24214     activate: function (evt) {
24215         if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
24216             //register mousewheel events specifically on the window and document
24217             var wheelListener = this.wheelListener;
24218             OpenLayers.Event.observe(window, "DOMMouseScroll", wheelListener);
24219             OpenLayers.Event.observe(window, "mousewheel", wheelListener);
24220             OpenLayers.Event.observe(document, "mousewheel", wheelListener);
24221             return true;
24222         } else {
24223             return false;
24224         }
24225     },
24226
24227     /**
24228      * Method: deactivate 
24229      */
24230     deactivate: function (evt) {
24231         if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
24232             // unregister mousewheel events specifically on the window and document
24233             var wheelListener = this.wheelListener;
24234             OpenLayers.Event.stopObserving(window, "DOMMouseScroll", wheelListener);
24235             OpenLayers.Event.stopObserving(window, "mousewheel", wheelListener);
24236             OpenLayers.Event.stopObserving(document, "mousewheel", wheelListener);
24237             return true;
24238         } else {
24239             return false;
24240         }
24241     },
24242
24243     CLASS_NAME: "OpenLayers.Handler.MouseWheel"
24244 });
24245 /* ======================================================================
24246     OpenLayers/Handler/Click.js
24247    ====================================================================== */
24248
24249 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
24250  * full list of contributors). Published under the 2-clause BSD license.
24251  * See license.txt in the OpenLayers distribution or repository for the
24252  * full text of the license. */
24253
24254 /**
24255  * @requires OpenLayers/Handler.js
24256  */
24257
24258 /**
24259  * Class: OpenLayers.Handler.Click
24260  * A handler for mouse clicks.  The intention of this handler is to give
24261  *     controls more flexibility with handling clicks.  Browsers trigger
24262  *     click events twice for a double-click.  In addition, the mousedown,
24263  *     mousemove, mouseup sequence fires a click event.  With this handler,
24264  *     controls can decide whether to ignore clicks associated with a double
24265  *     click.  By setting a <pixelTolerance>, controls can also ignore clicks
24266  *     that include a drag.  Create a new instance with the
24267  *     <OpenLayers.Handler.Click> constructor.
24268  * 
24269  * Inherits from:
24270  *  - <OpenLayers.Handler> 
24271  */
24272 OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
24273     /**
24274      * APIProperty: delay
24275      * {Number} Number of milliseconds between clicks before the event is
24276      *     considered a double-click.
24277      */
24278     delay: 300,
24279     
24280     /**
24281      * APIProperty: single
24282      * {Boolean} Handle single clicks.  Default is true.  If false, clicks
24283      * will not be reported.  If true, single-clicks will be reported.
24284      */
24285     single: true,
24286     
24287     /**
24288      * APIProperty: double
24289      * {Boolean} Handle double-clicks.  Default is false.
24290      */
24291     'double': false,
24292     
24293     /**
24294      * APIProperty: pixelTolerance
24295      * {Number} Maximum number of pixels between mouseup and mousedown for an
24296      *     event to be considered a click.  Default is 0.  If set to an
24297      *     integer value, clicks with a drag greater than the value will be
24298      *     ignored.  This property can only be set when the handler is
24299      *     constructed.
24300      */
24301     pixelTolerance: 0,
24302         
24303     /**
24304      * APIProperty: dblclickTolerance
24305      * {Number} Maximum distance in pixels between clicks for a sequence of 
24306      *     events to be considered a double click.  Default is 13.  If the
24307      *     distance between two clicks is greater than this value, a double-
24308      *     click will not be fired.
24309      */
24310     dblclickTolerance: 13,
24311         
24312     /**
24313      * APIProperty: stopSingle
24314      * {Boolean} Stop other listeners from being notified of clicks.  Default
24315      *     is false.  If true, any listeners registered before this one for 
24316      *     click or rightclick events will not be notified.
24317      */
24318     stopSingle: false,
24319     
24320     /**
24321      * APIProperty: stopDouble
24322      * {Boolean} Stop other listeners from being notified of double-clicks.
24323      *     Default is false.  If true, any click listeners registered before
24324      *     this one will not be notified of *any* double-click events.
24325      * 
24326      * The one caveat with stopDouble is that given a map with two click
24327      *     handlers, one with stopDouble true and the other with stopSingle
24328      *     true, the stopSingle handler should be activated last to get
24329      *     uniform cross-browser performance.  Since IE triggers one click
24330      *     with a dblclick and FF triggers two, if a stopSingle handler is
24331      *     activated first, all it gets in IE is a single click when the
24332      *     second handler stops propagation on the dblclick.
24333      */
24334     stopDouble: false,
24335
24336     /**
24337      * Property: timerId
24338      * {Number} The id of the timeout waiting to clear the <delayedCall>.
24339      */
24340     timerId: null,
24341     
24342     /**
24343      * Property: down
24344      * {Object} Object that store relevant information about the last
24345      *     mousedown or touchstart. Its 'xy' OpenLayers.Pixel property gives
24346      *     the average location of the mouse/touch event. Its 'touches'
24347      *     property records clientX/clientY of each touches.
24348      */
24349     down: null,
24350
24351     /**
24352      * Property: last
24353      * {Object} Object that store relevant information about the last
24354      *     mousemove or touchmove. Its 'xy' OpenLayers.Pixel property gives
24355      *     the average location of the mouse/touch event. Its 'touches'
24356      *     property records clientX/clientY of each touches.
24357      */
24358     last: null,
24359
24360     /** 
24361      * Property: first
24362      * {Object} When waiting for double clicks, this object will store 
24363      *     information about the first click in a two click sequence.
24364      */
24365     first: null,
24366
24367     /**
24368      * Property: rightclickTimerId
24369      * {Number} The id of the right mouse timeout waiting to clear the 
24370      *     <delayedEvent>.
24371      */
24372     rightclickTimerId: null,
24373     
24374     /**
24375      * Constructor: OpenLayers.Handler.Click
24376      * Create a new click handler.
24377      * 
24378      * Parameters:
24379      * control - {<OpenLayers.Control>} The control that is making use of
24380      *     this handler.  If a handler is being used without a control, the
24381      *     handler's setMap method must be overridden to deal properly with
24382      *     the map.
24383      * callbacks - {Object} An object with keys corresponding to callbacks
24384      *     that will be called by the handler. The callbacks should
24385      *     expect to receive a single argument, the click event.
24386      *     Callbacks for 'click' and 'dblclick' are supported.
24387      * options - {Object} Optional object whose properties will be set on the
24388      *     handler.
24389      */
24390     
24391     /**
24392      * Method: touchstart
24393      * Handle touchstart.
24394      *
24395      * Returns:
24396      * {Boolean} Continue propagating this event.
24397      */
24398     touchstart: function(evt) {
24399         this.startTouch();
24400         this.down = this.getEventInfo(evt);
24401         this.last = this.getEventInfo(evt);
24402         return true;
24403     },
24404     
24405     /**
24406      * Method: touchmove
24407      *    Store position of last move, because touchend event can have
24408      *    an empty "touches" property.
24409      *
24410      * Returns:
24411      * {Boolean} Continue propagating this event.
24412      */
24413     touchmove: function(evt) {
24414         this.last = this.getEventInfo(evt);
24415         return true;
24416     },
24417
24418     /**
24419      * Method: touchend
24420      *   Correctly set event xy property, and add lastTouches to have
24421      *   touches property from last touchstart or touchmove
24422      *
24423      * Returns:
24424      * {Boolean} Continue propagating this event.
24425      */
24426     touchend: function(evt) {
24427         // touchstart may not have been allowed to propagate
24428         if (this.down) {
24429             evt.xy = this.last.xy;
24430             evt.lastTouches = this.last.touches;
24431             this.handleSingle(evt);
24432             this.down = null;
24433         }
24434         return true;
24435     },
24436
24437     /**
24438      * Method: mousedown
24439      * Handle mousedown.
24440      *
24441      * Returns:
24442      * {Boolean} Continue propagating this event.
24443      */
24444     mousedown: function(evt) {
24445         this.down = this.getEventInfo(evt);
24446         this.last = this.getEventInfo(evt);
24447         return true;
24448     },
24449
24450     /**
24451      * Method: mouseup
24452      * Handle mouseup.  Installed to support collection of right mouse events.
24453      * 
24454      * Returns:
24455      * {Boolean} Continue propagating this event.
24456      */
24457     mouseup: function (evt) {
24458         var propagate = true;
24459
24460         // Collect right mouse clicks from the mouseup
24461         //  IE - ignores the second right click in mousedown so using
24462         //  mouseup instead
24463         if (this.checkModifiers(evt) && this.control.handleRightClicks &&
24464            OpenLayers.Event.isRightClick(evt)) {
24465             propagate = this.rightclick(evt);
24466         }
24467
24468         return propagate;
24469     },
24470     
24471     /**
24472      * Method: rightclick
24473      * Handle rightclick.  For a dblrightclick, we get two clicks so we need 
24474      *     to always register for dblrightclick to properly handle single 
24475      *     clicks.
24476      *     
24477      * Returns:
24478      * {Boolean} Continue propagating this event.
24479      */
24480     rightclick: function(evt) {
24481         if(this.passesTolerance(evt)) {
24482            if(this.rightclickTimerId != null) {
24483                 //Second click received before timeout this must be 
24484                 // a double click
24485                 this.clearTimer();
24486                 this.callback('dblrightclick', [evt]);
24487                 return !this.stopDouble;
24488             } else { 
24489                 //Set the rightclickTimerId, send evt only if double is 
24490                 // true else trigger single
24491                 var clickEvent = this['double'] ?
24492                     OpenLayers.Util.extend({}, evt) : 
24493                     this.callback('rightclick', [evt]);
24494
24495                 var delayedRightCall = OpenLayers.Function.bind(
24496                     this.delayedRightCall, 
24497                     this, 
24498                     clickEvent
24499                 );
24500                 this.rightclickTimerId = window.setTimeout(
24501                     delayedRightCall, this.delay
24502                 );
24503             } 
24504         }
24505         return !this.stopSingle;
24506     },
24507     
24508     /**
24509      * Method: delayedRightCall
24510      * Sets <rightclickTimerId> to null.  And optionally triggers the 
24511      *     rightclick callback if evt is set.
24512      */
24513     delayedRightCall: function(evt) {
24514         this.rightclickTimerId = null;
24515         if (evt) {
24516            this.callback('rightclick', [evt]);
24517         }
24518     },
24519     
24520     /**
24521      * Method: click
24522      * Handle click events from the browser.  This is registered as a listener
24523      *     for click events and should not be called from other events in this
24524      *     handler.
24525      *
24526      * Returns:
24527      * {Boolean} Continue propagating this event.
24528      */
24529     click: function(evt) {
24530         if (!this.last) {
24531             this.last = this.getEventInfo(evt);
24532         }
24533         this.handleSingle(evt);
24534         return !this.stopSingle;
24535     },
24536
24537     /**
24538      * Method: dblclick
24539      * Handle dblclick.  For a dblclick, we get two clicks in some browsers
24540      *     (FF) and one in others (IE).  So we need to always register for
24541      *     dblclick to properly handle single clicks.  This method is registered
24542      *     as a listener for the dblclick browser event.  It should *not* be
24543      *     called by other methods in this handler.
24544      *     
24545      * Returns:
24546      * {Boolean} Continue propagating this event.
24547      */
24548     dblclick: function(evt) {
24549         this.handleDouble(evt);
24550         return !this.stopDouble;
24551     },
24552     
24553     /** 
24554      * Method: handleDouble
24555      * Handle double-click sequence.
24556      */
24557     handleDouble: function(evt) {
24558         if (this.passesDblclickTolerance(evt)) {
24559             if (this["double"]) {
24560                 this.callback("dblclick", [evt]);
24561             }
24562             // to prevent a dblclick from firing the click callback in IE
24563             this.clearTimer();
24564         }
24565     },
24566     
24567     /** 
24568      * Method: handleSingle
24569      * Handle single click sequence.
24570      */
24571     handleSingle: function(evt) {
24572         if (this.passesTolerance(evt)) {
24573             if (this.timerId != null) {
24574                 // already received a click
24575                 if (this.last.touches && this.last.touches.length === 1) {
24576                     // touch device, no dblclick event - this may be a double
24577                     if (this["double"]) {
24578                         // on Android don't let the browser zoom on the page
24579                         OpenLayers.Event.preventDefault(evt);
24580                     }
24581                     this.handleDouble(evt);
24582                 }
24583                 // if we're not in a touch environment we clear the click timer
24584                 // if we've got a second touch, we'll get two touchend events
24585                 if (!this.last.touches || this.last.touches.length !== 2) {
24586                     this.clearTimer();
24587                 }
24588             } else {
24589                 // remember the first click info so we can compare to the second
24590                 this.first = this.getEventInfo(evt);
24591                 // set the timer, send evt only if single is true
24592                 //use a clone of the event object because it will no longer 
24593                 //be a valid event object in IE in the timer callback
24594                 var clickEvent = this.single ?
24595                     OpenLayers.Util.extend({}, evt) : null;
24596                 this.queuePotentialClick(clickEvent);
24597             }
24598         }
24599     },
24600     
24601     /** 
24602      * Method: queuePotentialClick
24603      * This method is separated out largely to make testing easier (so we
24604      *     don't have to override window.setTimeout)
24605      */
24606     queuePotentialClick: function(evt) {
24607         this.timerId = window.setTimeout(
24608             OpenLayers.Function.bind(this.delayedCall, this, evt),
24609             this.delay
24610         );
24611     },
24612
24613     /**
24614      * Method: passesTolerance
24615      * Determine whether the event is within the optional pixel tolerance.  Note
24616      *     that the pixel tolerance check only works if mousedown events get to
24617      *     the listeners registered here.  If they are stopped by other elements,
24618      *     the <pixelTolerance> will have no effect here (this method will always
24619      *     return true).
24620      *
24621      * Returns:
24622      * {Boolean} The click is within the pixel tolerance (if specified).
24623      */
24624     passesTolerance: function(evt) {
24625         var passes = true;
24626         if (this.pixelTolerance != null && this.down && this.down.xy) {
24627             passes = this.pixelTolerance >= this.down.xy.distanceTo(evt.xy);
24628             // for touch environments, we also enforce that all touches
24629             // start and end within the given tolerance to be considered a click
24630             if (passes && this.touch && 
24631                 this.down.touches.length === this.last.touches.length) {
24632                 // the touchend event doesn't come with touches, so we check
24633                 // down and last
24634                 for (var i=0, ii=this.down.touches.length; i<ii; ++i) {
24635                     if (this.getTouchDistance(
24636                             this.down.touches[i], 
24637                             this.last.touches[i]
24638                         ) > this.pixelTolerance) {
24639                         passes = false;
24640                         break;
24641                     }
24642                 }
24643             }
24644         }
24645         return passes;
24646     },
24647     
24648     /** 
24649      * Method: getTouchDistance
24650      *
24651      * Returns:
24652      * {Boolean} The pixel displacement between two touches.
24653      */
24654     getTouchDistance: function(from, to) {
24655         return Math.sqrt(
24656             Math.pow(from.clientX - to.clientX, 2) +
24657             Math.pow(from.clientY - to.clientY, 2)
24658         );
24659     },
24660     
24661     /**
24662      * Method: passesDblclickTolerance
24663      * Determine whether the event is within the optional double-cick pixel 
24664      *     tolerance.
24665      *
24666      * Returns:
24667      * {Boolean} The click is within the double-click pixel tolerance.
24668      */
24669     passesDblclickTolerance: function(evt) {
24670         var passes = true;
24671         if (this.down && this.first) {
24672             passes = this.down.xy.distanceTo(this.first.xy) <= this.dblclickTolerance;
24673         }
24674         return passes;
24675     },
24676
24677     /**
24678      * Method: clearTimer
24679      * Clear the timer and set <timerId> to null.
24680      */
24681     clearTimer: function() {
24682         if (this.timerId != null) {
24683             window.clearTimeout(this.timerId);
24684             this.timerId = null;
24685         }
24686         if (this.rightclickTimerId != null) {
24687             window.clearTimeout(this.rightclickTimerId);
24688             this.rightclickTimerId = null;
24689         }
24690     },
24691     
24692     /**
24693      * Method: delayedCall
24694      * Sets <timerId> to null.  And optionally triggers the click callback if
24695      *     evt is set.
24696      */
24697     delayedCall: function(evt) {
24698         this.timerId = null;
24699         if (evt) {
24700             this.callback("click", [evt]);
24701         }
24702     },
24703
24704     /**
24705      * Method: getEventInfo
24706      * This method allows us to store event information without storing the
24707      *     actual event.  In touch devices (at least), the same event is 
24708      *     modified between touchstart, touchmove, and touchend.
24709      *
24710      * Returns:
24711      * {Object} An object with event related info.
24712      */
24713     getEventInfo: function(evt) {
24714         var touches;
24715         if (evt.touches) {
24716             var len = evt.touches.length;
24717             touches = new Array(len);
24718             var touch;
24719             for (var i=0; i<len; i++) {
24720                 touch = evt.touches[i];
24721                 touches[i] = {
24722                     clientX: touch.olClientX,
24723                     clientY: touch.olClientY
24724                 };
24725             }
24726         }
24727         return {
24728             xy: evt.xy,
24729             touches: touches
24730         };
24731     },
24732
24733     /**
24734      * APIMethod: deactivate
24735      * Deactivate the handler.
24736      *
24737      * Returns:
24738      * {Boolean} The handler was successfully deactivated.
24739      */
24740     deactivate: function() {
24741         var deactivated = false;
24742         if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
24743             this.clearTimer();
24744             this.down = null;
24745             this.first = null;
24746             this.last = null;
24747             deactivated = true;
24748         }
24749         return deactivated;
24750     },
24751
24752     CLASS_NAME: "OpenLayers.Handler.Click"
24753 });
24754 /* ======================================================================
24755     OpenLayers/Control/Navigation.js
24756    ====================================================================== */
24757
24758 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
24759  * full list of contributors). Published under the 2-clause BSD license.
24760  * See license.txt in the OpenLayers distribution or repository for the
24761  * full text of the license. */
24762
24763 /**
24764  * @requires OpenLayers/Control/ZoomBox.js
24765  * @requires OpenLayers/Control/DragPan.js
24766  * @requires OpenLayers/Handler/MouseWheel.js
24767  * @requires OpenLayers/Handler/Click.js
24768  */
24769
24770 /**
24771  * Class: OpenLayers.Control.Navigation
24772  * The navigation control handles map browsing with mouse events (dragging,
24773  *     double-clicking, and scrolling the wheel).  Create a new navigation 
24774  *     control with the <OpenLayers.Control.Navigation> control.  
24775  * 
24776  *     Note that this control is added to the map by default (if no controls 
24777  *     array is sent in the options object to the <OpenLayers.Map> 
24778  *     constructor).
24779  * 
24780  * Inherits:
24781  *  - <OpenLayers.Control>
24782  */
24783 OpenLayers.Control.Navigation = OpenLayers.Class(OpenLayers.Control, {
24784
24785     /** 
24786      * Property: dragPan
24787      * {<OpenLayers.Control.DragPan>} 
24788      */
24789     dragPan: null,
24790
24791     /**
24792      * APIProperty: dragPanOptions
24793      * {Object} Options passed to the DragPan control.
24794      */
24795     dragPanOptions: null,
24796
24797     /**
24798      * Property: pinchZoom
24799      * {<OpenLayers.Control.PinchZoom>}
24800      */
24801     pinchZoom: null,
24802
24803     /**
24804      * APIProperty: pinchZoomOptions
24805      * {Object} Options passed to the PinchZoom control.
24806      */
24807     pinchZoomOptions: null,
24808
24809     /**
24810      * APIProperty: documentDrag
24811      * {Boolean} Allow panning of the map by dragging outside map viewport.
24812      *     Default is false.
24813      */
24814     documentDrag: false,
24815
24816     /** 
24817      * Property: zoomBox
24818      * {<OpenLayers.Control.ZoomBox>}
24819      */
24820     zoomBox: null,
24821
24822     /**
24823      * APIProperty: zoomBoxEnabled
24824      * {Boolean} Whether the user can draw a box to zoom
24825      */
24826     zoomBoxEnabled: true, 
24827
24828     /**
24829      * APIProperty: zoomWheelEnabled
24830      * {Boolean} Whether the mousewheel should zoom the map
24831      */
24832     zoomWheelEnabled: true,
24833     
24834     /**
24835      * Property: mouseWheelOptions
24836      * {Object} Options passed to the MouseWheel control (only useful if
24837      *     <zoomWheelEnabled> is set to true). Default is no options for maps
24838      *     with fractionalZoom set to true, otherwise
24839      *     {cumulative: false, interval: 50, maxDelta: 6} 
24840      */
24841     mouseWheelOptions: null,
24842
24843     /**
24844      * APIProperty: handleRightClicks
24845      * {Boolean} Whether or not to handle right clicks. Default is false.
24846      */
24847     handleRightClicks: false,
24848
24849     /**
24850      * APIProperty: zoomBoxKeyMask
24851      * {Integer} <OpenLayers.Handler> key code of the key, which has to be
24852      *    pressed, while drawing the zoom box with the mouse on the screen. 
24853      *    You should probably set handleRightClicks to true if you use this
24854      *    with MOD_CTRL, to disable the context menu for machines which use
24855      *    CTRL-Click as a right click.
24856      * Default: <OpenLayers.Handler.MOD_SHIFT>
24857      */
24858     zoomBoxKeyMask: OpenLayers.Handler.MOD_SHIFT,
24859     
24860     /**
24861      * APIProperty: autoActivate
24862      * {Boolean} Activate the control when it is added to a map.  Default is
24863      *     true.
24864      */
24865     autoActivate: true,
24866
24867     /**
24868      * Constructor: OpenLayers.Control.Navigation
24869      * Create a new navigation control
24870      * 
24871      * Parameters:
24872      * options - {Object} An optional object whose properties will be set on
24873      *                    the control
24874      */
24875     initialize: function(options) {
24876         this.handlers = {};
24877         OpenLayers.Control.prototype.initialize.apply(this, arguments);
24878     },
24879
24880     /**
24881      * Method: destroy
24882      * The destroy method is used to perform any clean up before the control
24883      * is dereferenced.  Typically this is where event listeners are removed
24884      * to prevent memory leaks.
24885      */
24886     destroy: function() {
24887         this.deactivate();
24888
24889         if (this.dragPan) {
24890             this.dragPan.destroy();
24891         }
24892         this.dragPan = null;
24893
24894         if (this.zoomBox) {
24895             this.zoomBox.destroy();
24896         }
24897         this.zoomBox = null;
24898
24899         if (this.pinchZoom) {
24900             this.pinchZoom.destroy();
24901         }
24902         this.pinchZoom = null;
24903
24904         OpenLayers.Control.prototype.destroy.apply(this,arguments);
24905     },
24906     
24907     /**
24908      * Method: activate
24909      */
24910     activate: function() {
24911         this.dragPan.activate();
24912         if (this.zoomWheelEnabled) {
24913             this.handlers.wheel.activate();
24914         }    
24915         this.handlers.click.activate();
24916         if (this.zoomBoxEnabled) {
24917             this.zoomBox.activate();
24918         }
24919         if (this.pinchZoom) {
24920             this.pinchZoom.activate();
24921         }
24922         return OpenLayers.Control.prototype.activate.apply(this,arguments);
24923     },
24924
24925     /**
24926      * Method: deactivate
24927      */
24928     deactivate: function() {
24929         if (this.pinchZoom) {
24930             this.pinchZoom.deactivate();
24931         }
24932         this.zoomBox.deactivate();
24933         this.dragPan.deactivate();
24934         this.handlers.click.deactivate();
24935         this.handlers.wheel.deactivate();
24936         return OpenLayers.Control.prototype.deactivate.apply(this,arguments);
24937     },
24938     
24939     /**
24940      * Method: draw
24941      */
24942     draw: function() {
24943         // disable right mouse context menu for support of right click events
24944         if (this.handleRightClicks) {
24945             this.map.viewPortDiv.oncontextmenu = OpenLayers.Function.False;
24946         }
24947
24948         var clickCallbacks = { 
24949             'click': this.defaultClick,
24950             'dblclick': this.defaultDblClick, 
24951             'dblrightclick': this.defaultDblRightClick 
24952         };
24953         var clickOptions = {
24954             'double': true, 
24955             'stopDouble': true
24956         };
24957         this.handlers.click = new OpenLayers.Handler.Click(
24958             this, clickCallbacks, clickOptions
24959         );
24960         this.dragPan = new OpenLayers.Control.DragPan(
24961             OpenLayers.Util.extend({
24962                 map: this.map,
24963                 documentDrag: this.documentDrag
24964             }, this.dragPanOptions)
24965         );
24966         this.zoomBox = new OpenLayers.Control.ZoomBox(
24967                     {map: this.map, keyMask: this.zoomBoxKeyMask});
24968         this.dragPan.draw();
24969         this.zoomBox.draw();
24970         var wheelOptions = this.map.fractionalZoom ? {} : {
24971             cumulative: false,
24972             interval: 50,
24973             maxDelta: 6
24974         };
24975         this.handlers.wheel = new OpenLayers.Handler.MouseWheel(
24976             this, {up : this.wheelUp, down: this.wheelDown},
24977             OpenLayers.Util.extend(wheelOptions, this.mouseWheelOptions)
24978         );
24979         if (OpenLayers.Control.PinchZoom) {
24980             this.pinchZoom = new OpenLayers.Control.PinchZoom(
24981                 OpenLayers.Util.extend(
24982                     {map: this.map}, this.pinchZoomOptions));
24983         }
24984     },
24985
24986     /**
24987      * Method: defaultClick
24988      *
24989      * Parameters:
24990      * evt - {Event}
24991      */
24992     defaultClick: function (evt) {
24993         if (evt.lastTouches && evt.lastTouches.length == 2) {
24994             this.map.zoomOut();
24995         }
24996     },
24997
24998     /**
24999      * Method: defaultDblClick 
25000      * 
25001      * Parameters:
25002      * evt - {Event} 
25003      */
25004     defaultDblClick: function (evt) {
25005         this.map.zoomTo(this.map.zoom + 1, evt.xy);
25006     },
25007
25008     /**
25009      * Method: defaultDblRightClick 
25010      * 
25011      * Parameters:
25012      * evt - {Event} 
25013      */
25014     defaultDblRightClick: function (evt) {
25015         this.map.zoomTo(this.map.zoom - 1, evt.xy);
25016     },
25017     
25018     /**
25019      * Method: wheelChange  
25020      *
25021      * Parameters:
25022      * evt - {Event}
25023      * deltaZ - {Integer}
25024      */
25025     wheelChange: function(evt, deltaZ) {
25026         if (!this.map.fractionalZoom) {
25027             deltaZ =  Math.round(deltaZ);
25028         }
25029         var currentZoom = this.map.getZoom(),
25030             newZoom = currentZoom + deltaZ;
25031         newZoom = Math.max(newZoom, 0);
25032         newZoom = Math.min(newZoom, this.map.getNumZoomLevels());
25033         if (newZoom === currentZoom) {
25034             return;
25035         }
25036         this.map.zoomTo(newZoom, evt.xy);
25037     },
25038
25039     /** 
25040      * Method: wheelUp
25041      * User spun scroll wheel up
25042      * 
25043      * Parameters:
25044      * evt - {Event}
25045      * delta - {Integer}
25046      */
25047     wheelUp: function(evt, delta) {
25048         this.wheelChange(evt, delta || 1);
25049     },
25050
25051     /** 
25052      * Method: wheelDown
25053      * User spun scroll wheel down
25054      * 
25055      * Parameters:
25056      * evt - {Event}
25057      * delta - {Integer}
25058      */
25059     wheelDown: function(evt, delta) {
25060         this.wheelChange(evt, delta || -1);
25061     },
25062     
25063     /**
25064      * Method: disableZoomBox
25065      */
25066     disableZoomBox : function() {
25067         this.zoomBoxEnabled = false;
25068         this.zoomBox.deactivate();       
25069     },
25070     
25071     /**
25072      * Method: enableZoomBox
25073      */
25074     enableZoomBox : function() {
25075         this.zoomBoxEnabled = true;
25076         if (this.active) {
25077             this.zoomBox.activate();
25078         }    
25079     },
25080     
25081     /**
25082      * Method: disableZoomWheel
25083      */
25084     
25085     disableZoomWheel : function() {
25086         this.zoomWheelEnabled = false;
25087         this.handlers.wheel.deactivate();       
25088     },
25089     
25090     /**
25091      * Method: enableZoomWheel
25092      */
25093     
25094     enableZoomWheel : function() {
25095         this.zoomWheelEnabled = true;
25096         if (this.active) {
25097             this.handlers.wheel.activate();
25098         }    
25099     },
25100
25101     CLASS_NAME: "OpenLayers.Control.Navigation"
25102 });
25103 /* ======================================================================
25104     OpenLayers/Events/buttonclick.js
25105    ====================================================================== */
25106
25107 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
25108  * full list of contributors). Published under the 2-clause BSD license.
25109  * See license.txt in the OpenLayers distribution or repository for the
25110  * full text of the license. */
25111
25112 /**
25113  * @requires OpenLayers/Events.js
25114  */
25115
25116 /**
25117  * Class: OpenLayers.Events.buttonclick
25118  * Extension event type for handling buttons on top of a dom element. This
25119  *     event type fires "buttonclick" on its <target> when a button was
25120  *     clicked. Buttons are detected by the "olButton" class.
25121  *
25122  * This event type makes sure that button clicks do not interfere with other
25123  *     events that are registered on the same <element>.
25124  *
25125  * Event types provided by this extension:
25126  * - *buttonclick* Triggered when a button is clicked. Listeners receive an
25127  *     object with a *buttonElement* property referencing the dom element of
25128  *     the clicked button, and an *buttonXY* property with the click position
25129  *     relative to the button.
25130  */
25131 OpenLayers.Events.buttonclick = OpenLayers.Class({
25132     
25133     /**
25134      * Property: target
25135      * {<OpenLayers.Events>} The events instance that the buttonclick event will
25136      * be triggered on.
25137      */
25138     target: null,
25139     
25140     /**
25141      * Property: events
25142      * {Array} Events to observe and conditionally stop from propagating when
25143      *     an element with the olButton class (or its olAlphaImg child) is
25144      *     clicked.
25145      */
25146     events: [
25147         'mousedown', 'mouseup', 'click', 'dblclick',
25148         'touchstart', 'touchmove', 'touchend', 'keydown'
25149     ],
25150     
25151     /**
25152      * Property: startRegEx
25153      * {RegExp} Regular expression to test Event.type for events that start
25154      *     a buttonclick sequence.
25155      */
25156     startRegEx: /^mousedown|touchstart$/,
25157
25158     /**
25159      * Property: cancelRegEx
25160      * {RegExp} Regular expression to test Event.type for events that cancel
25161      *     a buttonclick sequence.
25162      */
25163     cancelRegEx: /^touchmove$/,
25164
25165     /**
25166      * Property: completeRegEx
25167      * {RegExp} Regular expression to test Event.type for events that complete
25168      *     a buttonclick sequence.
25169      */
25170     completeRegEx: /^mouseup|touchend$/,
25171
25172     /**
25173      * Property: isDeviceTouchCapable
25174      * {Boolean} Tells whether the browser detects touch events.
25175      */
25176     isDeviceTouchCapable: 'ontouchstart' in window ||
25177         window.DocumentTouch && document instanceof window.DocumentTouch,
25178     
25179     /**
25180      * Property: startEvt
25181      * {Event} The event that started the click sequence
25182      */
25183     
25184     /**
25185      * Constructor: OpenLayers.Events.buttonclick
25186      * Construct a buttonclick event type. Applications are not supposed to
25187      *     create instances of this class - they are created on demand by
25188      *     <OpenLayers.Events> instances.
25189      *
25190      * Parameters:
25191      * target - {<OpenLayers.Events>} The events instance that the buttonclick
25192      *     event will be triggered on.
25193      */
25194     initialize: function(target) {
25195         this.target = target;
25196         for (var i=this.events.length-1; i>=0; --i) {
25197             this.target.register(this.events[i], this, this.buttonClick, {
25198                 extension: true
25199             });
25200         }
25201     },
25202     
25203     /**
25204      * Method: destroy
25205      */
25206     destroy: function() {
25207         for (var i=this.events.length-1; i>=0; --i) {
25208             this.target.unregister(this.events[i], this, this.buttonClick);
25209         }
25210         delete this.target;
25211     },
25212
25213     /**
25214      * Method: getPressedButton
25215      * Get the pressed button, if any. Returns undefined if no button
25216      * was pressed.
25217      *
25218      * Arguments:
25219      * element - {DOMElement} The event target.
25220      *
25221      * Returns:
25222      * {DOMElement} The button element, or undefined.
25223      */
25224     getPressedButton: function(element) {
25225         var depth = 3, // limit the search depth
25226             button;
25227         do {
25228             if(OpenLayers.Element.hasClass(element, "olButton")) {
25229                 // hit!
25230                 button = element;
25231                 break;
25232             }
25233             element = element.parentNode;
25234         } while(--depth > 0 && element);
25235         return button;
25236     },
25237     
25238     /**
25239      * Method: ignore
25240      * Check for event target elements that should be ignored by OpenLayers.
25241      *
25242      * Parameters:
25243      * element - {DOMElement} The event target.
25244      */
25245     ignore: function(element) {
25246         var depth = 3,
25247             ignore = false;
25248         do {
25249             if (element.nodeName.toLowerCase() === 'a') {
25250                 ignore = true;
25251                 break;
25252             }
25253             element = element.parentNode;
25254         } while (--depth > 0 && element);
25255         return ignore;
25256     },
25257
25258     /**
25259      * Method: buttonClick
25260      * Check if a button was clicked, and fire the buttonclick event
25261      *
25262      * Parameters:
25263      * evt - {Event}
25264      */
25265     buttonClick: function(evt) {
25266         var propagate = true,
25267             element = OpenLayers.Event.element(evt);
25268
25269         if (element &&
25270            (OpenLayers.Event.isLeftClick(evt) &&
25271             !this.isDeviceTouchCapable ||
25272             !~evt.type.indexOf("mouse"))) {
25273             // was a button pressed?
25274             var button = this.getPressedButton(element);
25275             if (button) {
25276                 if (evt.type === "keydown") {
25277                     switch (evt.keyCode) {
25278                     case OpenLayers.Event.KEY_RETURN:
25279                     case OpenLayers.Event.KEY_SPACE:
25280                         this.target.triggerEvent("buttonclick", {
25281                             buttonElement: button
25282                         });
25283                         OpenLayers.Event.stop(evt);
25284                         propagate = false;
25285                         break;
25286                     }
25287                 } else if (this.startEvt) {
25288                     if (this.completeRegEx.test(evt.type)) {
25289                         var pos = OpenLayers.Util.pagePosition(button);
25290                         var viewportElement = OpenLayers.Util.getViewportElement();
25291                         var scrollTop = window.pageYOffset || viewportElement.scrollTop;
25292                         var scrollLeft = window.pageXOffset || viewportElement.scrollLeft;
25293                         pos[0] = pos[0] - scrollLeft;
25294                         pos[1] = pos[1] - scrollTop;
25295                         
25296                         this.target.triggerEvent("buttonclick", {
25297                             buttonElement: button,
25298                             buttonXY: {
25299                                 x: this.startEvt.clientX - pos[0],
25300                                 y: this.startEvt.clientY - pos[1]
25301                             }
25302                         });
25303                     }
25304                     if (this.cancelRegEx.test(evt.type)) {
25305                         if (evt.touches && this.startEvt.touches &&
25306                                 (Math.abs(evt.touches[0].olClientX - this.startEvt.touches[0].olClientX) > 4 ||
25307                                 Math.abs(evt.touches[0].olClientY - this.startEvt.touches[0].olClientY)) > 4) {
25308                             delete this.startEvt;
25309                         }
25310                     }
25311                     OpenLayers.Event.stop(evt);
25312                     propagate = false;
25313                 }
25314                 if (this.startRegEx.test(evt.type)) {
25315                     this.startEvt = evt;
25316                     OpenLayers.Event.stop(evt);
25317                     propagate = false;
25318                 }
25319             } else {
25320                 propagate = !this.ignore(OpenLayers.Event.element(evt));
25321                 delete this.startEvt;
25322             }
25323         }
25324         return propagate;
25325     }
25326     
25327 });
25328 /* ======================================================================
25329     OpenLayers/Control/PanZoom.js
25330    ====================================================================== */
25331
25332 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
25333  * full list of contributors). Published under the 2-clause BSD license.
25334  * See license.txt in the OpenLayers distribution or repository for the
25335  * full text of the license. */
25336
25337
25338 /**
25339  * @requires OpenLayers/Control.js
25340  * @requires OpenLayers/Events/buttonclick.js
25341  */
25342
25343 /**
25344  * Class: OpenLayers.Control.PanZoom
25345  * The PanZoom is a visible control, composed of a
25346  * <OpenLayers.Control.PanPanel> and a <OpenLayers.Control.ZoomPanel>. By
25347  * default it is drawn in the upper left corner of the map.
25348  *
25349  * Inherits from:
25350  *  - <OpenLayers.Control>
25351  */
25352 OpenLayers.Control.PanZoom = OpenLayers.Class(OpenLayers.Control, {
25353
25354     /** 
25355      * APIProperty: slideFactor
25356      * {Integer} Number of pixels by which we'll pan the map in any direction 
25357      *     on clicking the arrow buttons.  If you want to pan by some ratio
25358      *     of the map dimensions, use <slideRatio> instead.
25359      */
25360     slideFactor: 50,
25361
25362     /** 
25363      * APIProperty: slideRatio
25364      * {Number} The fraction of map width/height by which we'll pan the map            
25365      *     on clicking the arrow buttons.  Default is null.  If set, will
25366      *     override <slideFactor>. E.g. if slideRatio is .5, then the Pan Up
25367      *     button will pan up half the map height. 
25368      */
25369     slideRatio: null,
25370
25371     /** 
25372      * Property: buttons
25373      * {Array(DOMElement)} Array of Button Divs 
25374      */
25375     buttons: null,
25376
25377     /** 
25378      * Property: position
25379      * {<OpenLayers.Pixel>} 
25380      */
25381     position: null,
25382
25383     /**
25384      * Constructor: OpenLayers.Control.PanZoom
25385      * 
25386      * Parameters:
25387      * options - {Object}
25388      */
25389     initialize: function(options) {
25390         this.position = new OpenLayers.Pixel(OpenLayers.Control.PanZoom.X,
25391                                              OpenLayers.Control.PanZoom.Y);
25392         OpenLayers.Control.prototype.initialize.apply(this, arguments);
25393     },
25394
25395     /**
25396      * APIMethod: destroy
25397      */
25398     destroy: function() {
25399         if (this.map && !this.outsideViewport) {
25400             this.map.events.unregister("buttonclick", this, this.onButtonClick);
25401         }
25402         this.removeButtons();
25403         this.buttons = null;
25404         this.position = null;
25405         OpenLayers.Control.prototype.destroy.apply(this, arguments);
25406     },
25407
25408     /** 
25409      * Method: setMap
25410      *
25411      * Properties:
25412      * map - {<OpenLayers.Map>} 
25413      */
25414     setMap: function(map) {
25415         OpenLayers.Control.prototype.setMap.apply(this, arguments);
25416         var target;
25417         if (this.outsideViewport) {
25418             this.events.attachToElement(this.div);
25419             target = this;
25420         } else {
25421             target = this.map;
25422         }
25423         target.events.register('buttonclick', this, this.onButtonClick);
25424     },
25425
25426     /**
25427      * Method: draw
25428      *
25429      * Parameters:
25430      * px - {<OpenLayers.Pixel>} 
25431      * 
25432      * Returns:
25433      * {DOMElement} A reference to the container div for the PanZoom control.
25434      */
25435     draw: function(px) {
25436         // initialize our internal div
25437         OpenLayers.Control.prototype.draw.apply(this, arguments);
25438         px = this.position;
25439
25440         // place the controls
25441         this.buttons = [];
25442
25443         var sz = {w: 18, h: 18};
25444         var centered = new OpenLayers.Pixel(px.x+sz.w/2, px.y);
25445
25446         this._addButton("panup", "north-mini.png", centered, sz);
25447         px.y = centered.y+sz.h;
25448         this._addButton("panleft", "west-mini.png", px, sz);
25449         this._addButton("panright", "east-mini.png", px.add(sz.w, 0), sz);
25450         this._addButton("pandown", "south-mini.png", 
25451                         centered.add(0, sz.h*2), sz);
25452         this._addButton("zoomin", "zoom-plus-mini.png", 
25453                         centered.add(0, sz.h*3+5), sz);
25454         this._addButton("zoomworld", "zoom-world-mini.png", 
25455                         centered.add(0, sz.h*4+5), sz);
25456         this._addButton("zoomout", "zoom-minus-mini.png", 
25457                         centered.add(0, sz.h*5+5), sz);
25458         return this.div;
25459     },
25460     
25461     /**
25462      * Method: _addButton
25463      * 
25464      * Parameters:
25465      * id - {String} 
25466      * img - {String} 
25467      * xy - {<OpenLayers.Pixel>} 
25468      * sz - {<OpenLayers.Size>} 
25469      * 
25470      * Returns:
25471      * {DOMElement} A Div (an alphaImageDiv, to be precise) that contains the
25472      *     image of the button, and has all the proper event handlers set.
25473      */
25474     _addButton:function(id, img, xy, sz) {
25475         var imgLocation = OpenLayers.Util.getImageLocation(img);
25476         var btn = OpenLayers.Util.createAlphaImageDiv(
25477                                     this.id + "_" + id, 
25478                                     xy, sz, imgLocation, "absolute");
25479         btn.style.cursor = "pointer";
25480         //we want to add the outer div
25481         this.div.appendChild(btn);
25482         btn.action = id;
25483         btn.className = "olButton";
25484     
25485         //we want to remember/reference the outer div
25486         this.buttons.push(btn);
25487         return btn;
25488     },
25489     
25490     /**
25491      * Method: _removeButton
25492      * 
25493      * Parameters:
25494      * btn - {Object}
25495      */
25496     _removeButton: function(btn) {
25497         this.div.removeChild(btn);
25498         OpenLayers.Util.removeItem(this.buttons, btn);
25499     },
25500     
25501     /**
25502      * Method: removeButtons
25503      */
25504     removeButtons: function() {
25505         for(var i=this.buttons.length-1; i>=0; --i) {
25506             this._removeButton(this.buttons[i]);
25507         }
25508     },
25509     
25510     /**
25511      * Method: onButtonClick
25512      *
25513      * Parameters:
25514      * evt - {Event}
25515      */
25516     onButtonClick: function(evt) {
25517         var btn = evt.buttonElement;
25518         switch (btn.action) {
25519             case "panup": 
25520                 this.map.pan(0, -this.getSlideFactor("h"));
25521                 break;
25522             case "pandown": 
25523                 this.map.pan(0, this.getSlideFactor("h"));
25524                 break;
25525             case "panleft": 
25526                 this.map.pan(-this.getSlideFactor("w"), 0);
25527                 break;
25528             case "panright": 
25529                 this.map.pan(this.getSlideFactor("w"), 0);
25530                 break;
25531             case "zoomin": 
25532                 this.map.zoomIn(); 
25533                 break;
25534             case "zoomout": 
25535                 this.map.zoomOut(); 
25536                 break;
25537             case "zoomworld": 
25538                 this.map.zoomToMaxExtent(); 
25539                 break;
25540         }
25541     },
25542     
25543     /**
25544      * Method: getSlideFactor
25545      *
25546      * Parameters:
25547      * dim - {String} "w" or "h" (for width or height).
25548      *
25549      * Returns:
25550      * {Number} The slide factor for panning in the requested direction.
25551      */
25552     getSlideFactor: function(dim) {
25553         return this.slideRatio ?
25554             this.map.getSize()[dim] * this.slideRatio :
25555             this.slideFactor;
25556     },
25557
25558     CLASS_NAME: "OpenLayers.Control.PanZoom"
25559 });
25560
25561 /**
25562  * Constant: X
25563  * {Integer}
25564  */
25565 OpenLayers.Control.PanZoom.X = 4;
25566
25567 /**
25568  * Constant: Y
25569  * {Integer}
25570  */
25571 OpenLayers.Control.PanZoom.Y = 4;
25572 /* ======================================================================
25573     OpenLayers/Handler/Pinch.js
25574    ====================================================================== */
25575
25576 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
25577  * full list of contributors). Published under the 2-clause BSD license.
25578  * See license.txt in the OpenLayers distribution or repository for the
25579  * full text of the license. */
25580
25581 /**
25582  * @requires OpenLayers/Handler.js
25583  */
25584
25585 /**
25586  * Class: OpenLayers.Handler.Pinch
25587  * The pinch handler is used to deal with sequences of browser events related
25588  *     to pinch gestures. The handler is used by controls that want to know
25589  *     when a pinch sequence begins, when a pinch is happening, and when it has
25590  *     finished.
25591  *
25592  * Controls that use the pinch handler typically construct it with callbacks
25593  *     for 'start', 'move', and 'done'.  Callbacks for these keys are
25594  *     called when the pinch begins, with each change, and when the pinch is
25595  *     done.
25596  *
25597  * Create a new pinch handler with the <OpenLayers.Handler.Pinch> constructor.
25598  *
25599  * Inherits from:
25600  *  - <OpenLayers.Handler>
25601  */
25602 OpenLayers.Handler.Pinch = OpenLayers.Class(OpenLayers.Handler, {
25603
25604     /**
25605      * Property: started
25606      * {Boolean} When a touchstart event is received, we want to record it,
25607      *     but not set 'pinching' until the touchmove get started after
25608      *     starting.
25609      */
25610     started: false,
25611
25612     /**
25613      * Property: stopDown
25614      * {Boolean} Stop propagation of touchstart events from getting to
25615      *     listeners on the same element. Default is false.
25616      */
25617     stopDown: false,
25618
25619     /**
25620      * Property: pinching
25621      * {Boolean}
25622      */
25623     pinching: false,
25624
25625     /**
25626      * Property: last
25627      * {Object} Object that store informations related to pinch last touch.
25628      */
25629     last: null,
25630
25631     /**
25632      * Property: start
25633      * {Object} Object that store informations related to pinch touchstart.
25634      */
25635     start: null,
25636
25637     /**
25638      * Constructor: OpenLayers.Handler.Pinch
25639      * Returns OpenLayers.Handler.Pinch
25640      *
25641      * Parameters:
25642      * control - {<OpenLayers.Control>} The control that is making use of
25643      *     this handler.  If a handler is being used without a control, the
25644      *     handlers setMap method must be overridden to deal properly with
25645      *     the map.
25646      * callbacks - {Object} An object containing functions to be called when
25647      *     the pinch operation start, change, or is finished. The callbacks
25648      *     should expect to receive an object argument, which contains
25649      *     information about scale, distance, and position of touch points.
25650      * options - {Object}
25651      */
25652
25653     /**
25654      * Method: touchstart
25655      * Handle touchstart events
25656      *
25657      * Parameters:
25658      * evt - {Event}
25659      *
25660      * Returns:
25661      * {Boolean} Let the event propagate.
25662      */
25663     touchstart: function(evt) {
25664         var propagate = true;
25665         this.pinching = false;
25666         if (OpenLayers.Event.isMultiTouch(evt)) {
25667             this.started = true;
25668             this.last = this.start = {
25669                 distance: this.getDistance(evt.touches),
25670                 delta: 0,
25671                 scale: 1
25672             };
25673             this.callback("start", [evt, this.start]);
25674             propagate = !this.stopDown;
25675         } else if (this.started) {
25676             // Some webkit versions send fake single-touch events during
25677             // multitouch, which cause the drag handler to trigger
25678             return false;
25679         } else {
25680             this.started = false;
25681             this.start = null;
25682             this.last = null;
25683         }
25684         // prevent document dragging
25685         OpenLayers.Event.preventDefault(evt);
25686         return propagate;
25687     },
25688
25689     /**
25690      * Method: touchmove
25691      * Handle touchmove events
25692      *
25693      * Parameters:
25694      * evt - {Event}
25695      *
25696      * Returns:
25697      * {Boolean} Let the event propagate.
25698      */
25699     touchmove: function(evt) {
25700         if (this.started && OpenLayers.Event.isMultiTouch(evt)) {
25701             this.pinching = true;
25702             var current = this.getPinchData(evt);
25703             this.callback("move", [evt, current]);
25704             this.last = current;
25705             // prevent document dragging
25706             OpenLayers.Event.stop(evt);
25707         } else if (this.started) {
25708             // Some webkit versions send fake single-touch events during
25709             // multitouch, which cause the drag handler to trigger
25710             return false;
25711         }
25712         return true;
25713     },
25714
25715     /**
25716      * Method: touchend
25717      * Handle touchend events
25718      *
25719      * Parameters:
25720      * evt - {Event}
25721      *
25722      * Returns:
25723      * {Boolean} Let the event propagate.
25724      */
25725     touchend: function(evt) {
25726         if (this.started && !OpenLayers.Event.isMultiTouch(evt)) {
25727             this.started = false;
25728             this.pinching = false;
25729             this.callback("done", [evt, this.start, this.last]);
25730             this.start = null;
25731             this.last = null;
25732             return false;
25733         }
25734         return true;
25735     },
25736
25737     /**
25738      * Method: activate
25739      * Activate the handler.
25740      *
25741      * Returns:
25742      * {Boolean} The handler was successfully activated.
25743      */
25744     activate: function() {
25745         var activated = false;
25746         if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
25747             this.pinching = false;
25748             activated = true;
25749         }
25750         return activated;
25751     },
25752
25753     /**
25754      * Method: deactivate
25755      * Deactivate the handler.
25756      *
25757      * Returns:
25758      * {Boolean} The handler was successfully deactivated.
25759      */
25760     deactivate: function() {
25761         var deactivated = false;
25762         if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
25763             this.started = false;
25764             this.pinching = false;
25765             this.start = null;
25766             this.last = null;
25767             deactivated = true;
25768         }
25769         return deactivated;
25770     },
25771
25772     /**
25773      * Method: getDistance
25774      * Get the distance in pixels between two touches.
25775      *
25776      * Parameters:
25777      * touches - {Array(Object)}
25778      *
25779      * Returns:
25780      * {Number} The distance in pixels.
25781      */
25782     getDistance: function(touches) {
25783         var t0 = touches[0];
25784         var t1 = touches[1];
25785         return Math.sqrt(
25786             Math.pow(t0.olClientX - t1.olClientX, 2) +
25787             Math.pow(t0.olClientY - t1.olClientY, 2)
25788         );
25789     },
25790
25791
25792     /**
25793      * Method: getPinchData
25794      * Get informations about the pinch event.
25795      *
25796      * Parameters:
25797      * evt - {Event}
25798      *
25799      * Returns:
25800      * {Object} Object that contains data about the current pinch.
25801      */
25802     getPinchData: function(evt) {
25803         var distance = this.getDistance(evt.touches);
25804         var scale = distance / this.start.distance;
25805         return {
25806             distance: distance,
25807             delta: this.last.distance - distance,
25808             scale: scale
25809         };
25810     },
25811
25812     CLASS_NAME: "OpenLayers.Handler.Pinch"
25813 });
25814
25815 /* ======================================================================
25816     OpenLayers/Control/PinchZoom.js
25817    ====================================================================== */
25818
25819 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
25820  * full list of contributors). Published under the 2-clause BSD license.
25821  * See license.txt in the OpenLayers distribution or repository for the
25822  * full text of the license. */
25823
25824 /**
25825  * @requires OpenLayers/Handler/Pinch.js
25826  */
25827
25828 /**
25829  * Class: OpenLayers.Control.PinchZoom
25830  *
25831  * Inherits:
25832  *  - <OpenLayers.Control>
25833  */
25834 OpenLayers.Control.PinchZoom = OpenLayers.Class(OpenLayers.Control, {
25835
25836     /** 
25837      * Property: type
25838      * {OpenLayers.Control.TYPES}
25839      */
25840     type: OpenLayers.Control.TYPE_TOOL,
25841
25842     /**
25843      * Property: pinchOrigin
25844      * {Object} Cached object representing the pinch start (in pixels).
25845      */
25846     pinchOrigin: null,    
25847     
25848     /**
25849      * Property: currentCenter
25850      * {Object} Cached object representing the latest pinch center (in pixels).
25851      */
25852     currentCenter: null,    
25853
25854     /**
25855      * APIProperty: autoActivate
25856      * {Boolean} Activate the control when it is added to a map.  Default is
25857      *     true.
25858      */
25859     autoActivate: true,
25860
25861     /**
25862      * APIProperty: preserveCenter
25863      * {Boolean} Set this to true if you don't want the map center to change
25864      *     while pinching. For example you may want to set preserveCenter to
25865      *     true when the user location is being watched and you want to preserve
25866      *     the user location at the center of the map even if he zooms in or
25867      *     out using pinch. This property's value can be changed any time on an
25868      *     existing instance. Default is false.
25869      */
25870     preserveCenter: false,
25871     
25872     /**
25873      * APIProperty: handlerOptions
25874      * {Object} Used to set non-default properties on the pinch handler
25875      */
25876
25877     /**
25878      * Constructor: OpenLayers.Control.PinchZoom
25879      * Create a control for zooming with pinch gestures.  This works on devices
25880      *     with multi-touch support.
25881      *
25882      * Parameters:
25883      * options - {Object} An optional object whose properties will be set on
25884      *                    the control
25885      */
25886     initialize: function(options) {
25887         OpenLayers.Control.prototype.initialize.apply(this, arguments);
25888         this.handler = new OpenLayers.Handler.Pinch(this, {
25889             start: this.pinchStart,
25890             move: this.pinchMove,
25891             done: this.pinchDone
25892         }, this.handlerOptions);
25893     },
25894     
25895     /**
25896      * Method: pinchStart
25897      *
25898      * Parameters:
25899      * evt - {Event}
25900      * pinchData - {Object} pinch data object related to the current touchmove
25901      *     of the pinch gesture. This give us the current scale of the pinch.
25902      */
25903     pinchStart: function(evt, pinchData) {
25904         var xy = (this.preserveCenter) ?
25905             this.map.getPixelFromLonLat(this.map.getCenter()) : evt.xy;
25906         this.pinchOrigin = xy;
25907         this.currentCenter = xy;
25908     },
25909     
25910     /**
25911      * Method: pinchMove
25912      *
25913      * Parameters:
25914      * evt - {Event}
25915      * pinchData - {Object} pinch data object related to the current touchmove
25916      *     of the pinch gesture. This give us the current scale of the pinch.
25917      */
25918     pinchMove: function(evt, pinchData) {
25919         var scale = pinchData.scale;
25920         var containerOrigin = this.map.layerContainerOriginPx;
25921         var pinchOrigin = this.pinchOrigin;
25922         var current = (this.preserveCenter) ?
25923             this.map.getPixelFromLonLat(this.map.getCenter()) : evt.xy;
25924
25925         var dx = Math.round((containerOrigin.x + current.x - pinchOrigin.x) + (scale - 1) * (containerOrigin.x - pinchOrigin.x));
25926         var dy = Math.round((containerOrigin.y + current.y - pinchOrigin.y) + (scale - 1) * (containerOrigin.y - pinchOrigin.y));
25927
25928         this.map.applyTransform(dx, dy, scale);
25929         this.currentCenter = current;
25930     },
25931
25932     /**
25933      * Method: pinchDone
25934      *
25935      * Parameters:
25936      * evt - {Event}
25937      * start - {Object} pinch data object related to the touchstart event that
25938      *     started the pinch gesture.
25939      * last - {Object} pinch data object related to the last touchmove event
25940      *     of the pinch gesture. This give us the final scale of the pinch.
25941      */
25942     pinchDone: function(evt, start, last) {
25943         this.map.applyTransform();
25944         var zoom = this.map.getZoomForResolution(this.map.getResolution() / last.scale, true);
25945         if (zoom !== this.map.getZoom() || !this.currentCenter.equals(this.pinchOrigin)) {
25946             var resolution = this.map.getResolutionForZoom(zoom);
25947
25948             var location = this.map.getLonLatFromPixel(this.pinchOrigin);
25949             var zoomPixel = this.currentCenter;        
25950             var size = this.map.getSize();
25951
25952             location.lon += resolution * ((size.w / 2) - zoomPixel.x);
25953             location.lat -= resolution * ((size.h / 2) - zoomPixel.y);
25954
25955             // Force a reflow before calling setCenter. This is to work
25956             // around an issue occurring in iOS.
25957             //
25958             // See https://github.com/openlayers/ol2/pull/351.
25959             //
25960             // Without a reflow setting the layer container div's top left
25961             // style properties to "0px" - as done in Map.moveTo when zoom
25962             // is changed - won't actually correctly reposition the layer
25963             // container div.
25964             //
25965             // Also, we need to use a statement that the Google Closure
25966             // compiler won't optimize away.
25967             this.map.div.clientWidth = this.map.div.clientWidth;
25968
25969             this.map.setCenter(location, zoom);
25970         }
25971     },
25972
25973     CLASS_NAME: "OpenLayers.Control.PinchZoom"
25974
25975 });
25976 /* ======================================================================
25977     OpenLayers/Control/TouchNavigation.js
25978    ====================================================================== */
25979
25980 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
25981  * full list of contributors). Published under the 2-clause BSD license.
25982  * See license.txt in the OpenLayers distribution or repository for the
25983  * full text of the license. */
25984
25985 /**
25986  * @requires OpenLayers/Control/DragPan.js
25987  * @requires OpenLayers/Control/PinchZoom.js
25988  * @requires OpenLayers/Handler/Click.js
25989  */
25990
25991 /**
25992  * Class: OpenLayers.Control.TouchNavigation
25993  * The navigation control handles map browsing with touch events (dragging,
25994  *     double-tapping, tap with two fingers, and pinch zoom).  Create a new 
25995  *     control with the <OpenLayers.Control.TouchNavigation> constructor.
25996  *
25997  * If you’re only targeting touch enabled devices with your mapping application,
25998  *     you can create a map with only a TouchNavigation control. The 
25999  *     <OpenLayers.Control.Navigation> control is mobile ready by default, but 
26000  *     you can generate a smaller build of the library by only including this
26001  *     touch navigation control if you aren't concerned about mouse interaction.
26002  *
26003  * Inherits:
26004  *  - <OpenLayers.Control>
26005  */
26006 OpenLayers.Control.TouchNavigation = OpenLayers.Class(OpenLayers.Control, {
26007
26008     /**
26009      * Property: dragPan
26010      * {<OpenLayers.Control.DragPan>}
26011      */
26012     dragPan: null,
26013
26014     /**
26015      * APIProperty: dragPanOptions
26016      * {Object} Options passed to the DragPan control.
26017      */
26018     dragPanOptions: null,
26019
26020     /**
26021      * Property: pinchZoom
26022      * {<OpenLayers.Control.PinchZoom>}
26023      */
26024     pinchZoom: null,
26025
26026     /**
26027      * APIProperty: pinchZoomOptions
26028      * {Object} Options passed to the PinchZoom control.
26029      */
26030     pinchZoomOptions: null,
26031
26032     /**
26033      * APIProperty: clickHandlerOptions
26034      * {Object} Options passed to the Click handler.
26035      */
26036     clickHandlerOptions: null,
26037
26038     /**
26039      * APIProperty: documentDrag
26040      * {Boolean} Allow panning of the map by dragging outside map viewport.
26041      *     Default is false.
26042      */
26043     documentDrag: false,
26044
26045     /**
26046      * APIProperty: autoActivate
26047      * {Boolean} Activate the control when it is added to a map.  Default is
26048      *     true.
26049      */
26050     autoActivate: true,
26051
26052     /**
26053      * Constructor: OpenLayers.Control.TouchNavigation
26054      * Create a new navigation control
26055      *
26056      * Parameters:
26057      * options - {Object} An optional object whose properties will be set on
26058      *                    the control
26059      */
26060     initialize: function(options) {
26061         this.handlers = {};
26062         OpenLayers.Control.prototype.initialize.apply(this, arguments);
26063     },
26064
26065     /**
26066      * Method: destroy
26067      * The destroy method is used to perform any clean up before the control
26068      * is dereferenced.  Typically this is where event listeners are removed
26069      * to prevent memory leaks.
26070      */
26071     destroy: function() {
26072         this.deactivate();
26073         if(this.dragPan) {
26074             this.dragPan.destroy();
26075         }
26076         this.dragPan = null;
26077         if (this.pinchZoom) {
26078             this.pinchZoom.destroy();
26079             delete this.pinchZoom;
26080         }
26081         OpenLayers.Control.prototype.destroy.apply(this,arguments);
26082     },
26083
26084     /**
26085      * Method: activate
26086      */
26087     activate: function() {
26088         if(OpenLayers.Control.prototype.activate.apply(this,arguments)) {
26089             this.dragPan.activate();
26090             this.handlers.click.activate();
26091             this.pinchZoom.activate();
26092             return true;
26093         }
26094         return false;
26095     },
26096
26097     /**
26098      * Method: deactivate
26099      */
26100     deactivate: function() {
26101         if(OpenLayers.Control.prototype.deactivate.apply(this,arguments)) {
26102             this.dragPan.deactivate();
26103             this.handlers.click.deactivate();
26104             this.pinchZoom.deactivate();
26105             return true;
26106         }
26107         return false;
26108     },
26109     
26110     /**
26111      * Method: draw
26112      */
26113     draw: function() {
26114         var clickCallbacks = {
26115             click: this.defaultClick,
26116             dblclick: this.defaultDblClick
26117         };
26118         var clickOptions = OpenLayers.Util.extend({
26119             "double": true,
26120             stopDouble: true,
26121             pixelTolerance: 2
26122         }, this.clickHandlerOptions);
26123         this.handlers.click = new OpenLayers.Handler.Click(
26124             this, clickCallbacks, clickOptions
26125         );
26126         this.dragPan = new OpenLayers.Control.DragPan(
26127             OpenLayers.Util.extend({
26128                 map: this.map,
26129                 documentDrag: this.documentDrag
26130             }, this.dragPanOptions)
26131         );
26132         this.dragPan.draw();
26133         this.pinchZoom = new OpenLayers.Control.PinchZoom(
26134             OpenLayers.Util.extend({map: this.map}, this.pinchZoomOptions)
26135         );
26136     },
26137
26138     /**
26139      * Method: defaultClick
26140      *
26141      * Parameters:
26142      * evt - {Event}
26143      */
26144     defaultClick: function (evt) {
26145         if(evt.lastTouches && evt.lastTouches.length == 2) {
26146             this.map.zoomOut();
26147         }
26148     },
26149
26150     /**
26151      * Method: defaultDblClick
26152      *
26153      * Parameters:
26154      * evt - {Event}
26155      */
26156     defaultDblClick: function (evt) {
26157         this.map.zoomTo(this.map.zoom + 1, evt.xy);
26158     },
26159
26160     CLASS_NAME: "OpenLayers.Control.TouchNavigation"
26161 });
26162 /* ======================================================================
26163     OpenLayers/Control/Attribution.js
26164    ====================================================================== */
26165
26166 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
26167  * full list of contributors). Published under the 2-clause BSD license.
26168  * See license.txt in the OpenLayers distribution or repository for the
26169  * full text of the license. */
26170
26171 /**
26172  * @requires OpenLayers/Control.js
26173  */
26174
26175 /**
26176  * Class: OpenLayers.Control.Attribution
26177  * The attribution control adds attribution from layers to the map display. 
26178  * It uses 'attribution' property of each layer.
26179  *
26180  * Inherits from:
26181  *  - <OpenLayers.Control>
26182  */
26183 OpenLayers.Control.Attribution = 
26184   OpenLayers.Class(OpenLayers.Control, {
26185     
26186     /**
26187      * APIProperty: separator
26188      * {String} String used to separate layers.
26189      */
26190     separator: ", ",
26191
26192     /**
26193      * APIProperty: template
26194      * {String} Template for the global attribution markup. This has to include the
26195      *     substring "${layers}", which will be replaced by the layer specific
26196      *     attributions, separated by <separator>. The default is "${layers}".
26197      */
26198     template: "${layers}",
26199
26200     /**
26201      * APIProperty: layerTemplate
26202      * {String} Template for the layer specific attribution. This has to include
26203      *     the substrings "${href}" and "${title}", which will be replaced by
26204      *     the layer specific attribution object properties.
26205      *     The default is '<a href="${href}" target="_blank">${title}</a>'.
26206      */
26207     layerTemplate: '<a href="${href}" target="_blank">${title}</a>',
26208
26209     /**
26210      * Constructor: OpenLayers.Control.Attribution 
26211      * 
26212      * Parameters:
26213      * options - {Object} Options for control.
26214      */
26215
26216     /** 
26217      * Method: destroy
26218      * Destroy control.
26219      */
26220     destroy: function() {
26221         this.map.events.un({
26222             "removelayer": this.updateAttribution,
26223             "addlayer": this.updateAttribution,
26224             "changelayer": this.updateAttribution,
26225             "changebaselayer": this.updateAttribution,
26226             scope: this
26227         });
26228         
26229         OpenLayers.Control.prototype.destroy.apply(this, arguments);
26230     },    
26231     
26232     /**
26233      * Method: draw
26234      * Initialize control.
26235      * 
26236      * Returns: 
26237      * {DOMElement} A reference to the DIV DOMElement containing the control
26238      */    
26239     draw: function() {
26240         OpenLayers.Control.prototype.draw.apply(this, arguments);
26241         
26242         this.map.events.on({
26243             'changebaselayer': this.updateAttribution,
26244             'changelayer': this.updateAttribution,
26245             'addlayer': this.updateAttribution,
26246             'removelayer': this.updateAttribution,
26247             scope: this
26248         });
26249         this.updateAttribution();
26250         
26251         return this.div;    
26252     },
26253
26254     /**
26255      * Method: updateAttribution
26256      * Update attribution string.
26257      */
26258     updateAttribution: function() {
26259         var attributions = [], attribution;
26260         if (this.map && this.map.layers) {
26261             for(var i=0, len=this.map.layers.length; i<len; i++) {
26262                 var layer = this.map.layers[i];
26263                 if (layer.attribution && layer.getVisibility()) {
26264                     attribution = (typeof layer.attribution == "object") ?
26265                         OpenLayers.String.format(
26266                             this.layerTemplate, layer.attribution) :
26267                         layer.attribution;
26268                     // add attribution only if attribution text is unique
26269                     if (OpenLayers.Util.indexOf(
26270                                     attributions, attribution) === -1) {
26271                         attributions.push( attribution );
26272                     }
26273                 }
26274             } 
26275             this.div.innerHTML = OpenLayers.String.format(this.template, {
26276                 layers: attributions.join(this.separator)
26277             });
26278         }
26279     },
26280
26281     CLASS_NAME: "OpenLayers.Control.Attribution"
26282 });
26283 /* ======================================================================
26284     OpenLayers/Control/Zoom.js
26285    ====================================================================== */
26286
26287 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
26288  * full list of contributors). Published under the 2-clause BSD license.
26289  * See license.txt in the OpenLayers distribution or repository for the
26290  * full text of the license. */
26291
26292 /**
26293  * @requires OpenLayers/Control.js
26294  * @requires OpenLayers/Events/buttonclick.js
26295  */
26296
26297 /**
26298  * Class: OpenLayers.Control.Zoom
26299  * The Zoom control is a pair of +/- links for zooming in and out.
26300  *
26301  * Inherits from:
26302  *  - <OpenLayers.Control>
26303  */
26304 OpenLayers.Control.Zoom = OpenLayers.Class(OpenLayers.Control, {
26305     
26306     /**
26307      * APIProperty: zoomInText
26308      * {String}
26309      * Text for zoom-in link.  Default is "+".
26310      */
26311     zoomInText: "+",
26312
26313     /**
26314      * APIProperty: zoomInId
26315      * {String}
26316      * Instead of having the control create a zoom in link, you can provide 
26317      *     the identifier for an anchor element already added to the document.
26318      *     By default, an element with id "olZoomInLink" will be searched for
26319      *     and used if it exists.
26320      */
26321     zoomInId: "olZoomInLink",
26322
26323     /**
26324      * APIProperty: zoomOutText
26325      * {String}
26326      * Text for zoom-out link.  Default is "\u2212".
26327      */
26328     zoomOutText: "\u2212",
26329
26330     /**
26331      * APIProperty: zoomOutId
26332      * {String}
26333      * Instead of having the control create a zoom out link, you can provide 
26334      *     the identifier for an anchor element already added to the document.
26335      *     By default, an element with id "olZoomOutLink" will be searched for
26336      *     and used if it exists.
26337      */
26338     zoomOutId: "olZoomOutLink",
26339
26340     /**
26341      * Method: draw
26342      *
26343      * Returns:
26344      * {DOMElement} A reference to the DOMElement containing the zoom links.
26345      */
26346     draw: function() {
26347         var div = OpenLayers.Control.prototype.draw.apply(this),
26348             links = this.getOrCreateLinks(div),
26349             zoomIn = links.zoomIn,
26350             zoomOut = links.zoomOut,
26351             eventsInstance = this.map.events;
26352         
26353         if (zoomOut.parentNode !== div) {
26354             eventsInstance = this.events;
26355             eventsInstance.attachToElement(zoomOut.parentNode);
26356         }
26357         eventsInstance.register("buttonclick", this, this.onZoomClick);
26358         
26359         this.zoomInLink = zoomIn;
26360         this.zoomOutLink = zoomOut;
26361         return div;
26362     },
26363     
26364     /**
26365      * Method: getOrCreateLinks
26366      * 
26367      * Parameters:
26368      * el - {DOMElement}
26369      *
26370      * Return: 
26371      * {Object} Object with zoomIn and zoomOut properties referencing links.
26372      */
26373     getOrCreateLinks: function(el) {
26374         var zoomIn = document.getElementById(this.zoomInId),
26375             zoomOut = document.getElementById(this.zoomOutId);
26376         if (!zoomIn) {
26377             zoomIn = document.createElement("a");
26378             zoomIn.href = "#zoomIn";
26379             zoomIn.appendChild(document.createTextNode(this.zoomInText));
26380             zoomIn.className = "olControlZoomIn";
26381             el.appendChild(zoomIn);
26382         }
26383         OpenLayers.Element.addClass(zoomIn, "olButton");
26384         if (!zoomOut) {
26385             zoomOut = document.createElement("a");
26386             zoomOut.href = "#zoomOut";
26387             zoomOut.appendChild(document.createTextNode(this.zoomOutText));
26388             zoomOut.className = "olControlZoomOut";
26389             el.appendChild(zoomOut);
26390         }
26391         OpenLayers.Element.addClass(zoomOut, "olButton");
26392         return {
26393             zoomIn: zoomIn, zoomOut: zoomOut
26394         };
26395     },
26396     
26397     /**
26398      * Method: onZoomClick
26399      * Called when zoomin/out link is clicked.
26400      */
26401     onZoomClick: function(evt) {
26402         var button = evt.buttonElement;
26403         if (button === this.zoomInLink) {
26404             this.map.zoomIn();
26405         } else if (button === this.zoomOutLink) {
26406             this.map.zoomOut();
26407         }
26408     },
26409
26410     /** 
26411      * Method: destroy
26412      * Clean up.
26413      */
26414     destroy: function() {
26415         if (this.map) {
26416             this.map.events.unregister("buttonclick", this, this.onZoomClick);
26417         }
26418         delete this.zoomInLink;
26419         delete this.zoomOutLink;
26420         OpenLayers.Control.prototype.destroy.apply(this);
26421     },
26422
26423     CLASS_NAME: "OpenLayers.Control.Zoom"
26424 });
26425 /* ======================================================================
26426     OpenLayers/Handler/Feature.js
26427    ====================================================================== */
26428
26429 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
26430  * full list of contributors). Published under the 2-clause BSD license.
26431  * See license.txt in the OpenLayers distribution or repository for the
26432  * full text of the license. */
26433
26434
26435 /**
26436  * @requires OpenLayers/Handler.js
26437  */
26438
26439 /**
26440  * Class: OpenLayers.Handler.Feature 
26441  * Handler to respond to mouse events related to a drawn feature.  Callbacks
26442  *     with the following keys will be notified of the following events
26443  *     associated with features: click, clickout, over, out, and dblclick.
26444  *
26445  * This handler stops event propagation for mousedown and mouseup if those
26446  *     browser events target features that can be selected.
26447  *
26448  * Inherits from:
26449  *  - <OpenLayers.Handler>
26450  */
26451 OpenLayers.Handler.Feature = OpenLayers.Class(OpenLayers.Handler, {
26452
26453     /**
26454      * Property: EVENTMAP
26455      * {Object} A object mapping the browser events to objects with callback
26456      *     keys for in and out.
26457      */
26458     EVENTMAP: {
26459         'click': {'in': 'click', 'out': 'clickout'},
26460         'mousemove': {'in': 'over', 'out': 'out'},
26461         'dblclick': {'in': 'dblclick', 'out': null},
26462         'mousedown': {'in': null, 'out': null},
26463         'mouseup': {'in': null, 'out': null},
26464         'touchstart': {'in': 'click', 'out': 'clickout'}
26465     },
26466
26467     /**
26468      * Property: feature
26469      * {<OpenLayers.Feature.Vector>} The last feature that was hovered.
26470      */
26471     feature: null,
26472
26473     /**
26474      * Property: lastFeature
26475      * {<OpenLayers.Feature.Vector>} The last feature that was handled.
26476      */
26477     lastFeature: null,
26478
26479     /**
26480      * Property: down
26481      * {<OpenLayers.Pixel>} The location of the last mousedown.
26482      */
26483     down: null,
26484
26485     /**
26486      * Property: up
26487      * {<OpenLayers.Pixel>} The location of the last mouseup.
26488      */
26489     up: null,
26490     
26491     /**
26492      * Property: clickTolerance
26493      * {Number} The number of pixels the mouse can move between mousedown
26494      *     and mouseup for the event to still be considered a click.
26495      *     Dragging the map should not trigger the click and clickout callbacks
26496      *     unless the map is moved by less than this tolerance. Defaults to 4.
26497      */
26498     clickTolerance: 4,
26499
26500     /**
26501      * Property: geometryTypes
26502      * To restrict dragging to a limited set of geometry types, send a list
26503      * of strings corresponding to the geometry class names.
26504      * 
26505      * @type Array(String)
26506      */
26507     geometryTypes: null,
26508
26509     /**
26510      * Property: stopClick
26511      * {Boolean} If stopClick is set to true, handled clicks do not
26512      *      propagate to other click listeners. Otherwise, handled clicks
26513      *      do propagate. Unhandled clicks always propagate, whatever the
26514      *      value of stopClick. Defaults to true.
26515      */
26516     stopClick: true,
26517
26518     /**
26519      * Property: stopDown
26520      * {Boolean} If stopDown is set to true, handled mousedowns do not
26521      *      propagate to other mousedown listeners. Otherwise, handled
26522      *      mousedowns do propagate. Unhandled mousedowns always propagate,
26523      *      whatever the value of stopDown. Defaults to true.
26524      */
26525     stopDown: true,
26526
26527     /**
26528      * Property: stopUp
26529      * {Boolean} If stopUp is set to true, handled mouseups do not
26530      *      propagate to other mouseup listeners. Otherwise, handled mouseups
26531      *      do propagate. Unhandled mouseups always propagate, whatever the
26532      *      value of stopUp. Defaults to false.
26533      */
26534     stopUp: false,
26535     
26536     /**
26537      * Constructor: OpenLayers.Handler.Feature
26538      *
26539      * Parameters:
26540      * control - {<OpenLayers.Control>} 
26541      * layer - {<OpenLayers.Layer.Vector>}
26542      * callbacks - {Object} An object with a 'over' property whos value is
26543      *     a function to be called when the mouse is over a feature. The 
26544      *     callback should expect to receive a single argument, the feature.
26545      * options - {Object} 
26546      */
26547     initialize: function(control, layer, callbacks, options) {
26548         OpenLayers.Handler.prototype.initialize.apply(this, [control, callbacks, options]);
26549         this.layer = layer;
26550     },
26551
26552     /**
26553      * Method: touchstart
26554      * Handle touchstart events
26555      *
26556      * Parameters:
26557      * evt - {Event}
26558      *
26559      * Returns:
26560      * {Boolean} Let the event propagate.
26561      */
26562     touchstart: function(evt) {
26563         this.startTouch(); 
26564         return OpenLayers.Event.isMultiTouch(evt) ?
26565                 true : this.mousedown(evt);
26566     },
26567
26568     /**
26569      * Method: touchmove
26570      * Handle touchmove events. We just prevent the browser default behavior,
26571      *    for Android Webkit not to select text when moving the finger after
26572      *    selecting a feature.
26573      *
26574      * Parameters:
26575      * evt - {Event}
26576      */
26577     touchmove: function(evt) {
26578         OpenLayers.Event.preventDefault(evt);
26579     },
26580
26581     /**
26582      * Method: mousedown
26583      * Handle mouse down.  Stop propagation if a feature is targeted by this
26584      *     event (stops map dragging during feature selection).
26585      * 
26586      * Parameters:
26587      * evt - {Event} 
26588      */
26589     mousedown: function(evt) {
26590         // Feature selection is only done with a left click. Other handlers may stop the
26591         // propagation of left-click mousedown events but not right-click mousedown events.
26592         // This mismatch causes problems when comparing the location of the down and up
26593         // events in the click function so it is important ignore right-clicks.
26594         if (OpenLayers.Event.isLeftClick(evt) || OpenLayers.Event.isSingleTouch(evt)) {
26595             this.down = evt.xy;
26596         }
26597         return this.handle(evt) ? !this.stopDown : true;
26598     },
26599     
26600     /**
26601      * Method: mouseup
26602      * Handle mouse up.  Stop propagation if a feature is targeted by this
26603      *     event.
26604      * 
26605      * Parameters:
26606      * evt - {Event} 
26607      */
26608     mouseup: function(evt) {
26609         this.up = evt.xy;
26610         return this.handle(evt) ? !this.stopUp : true;
26611     },
26612
26613     /**
26614      * Method: click
26615      * Handle click.  Call the "click" callback if click on a feature,
26616      *     or the "clickout" callback if click outside any feature.
26617      * 
26618      * Parameters:
26619      * evt - {Event} 
26620      *
26621      * Returns:
26622      * {Boolean}
26623      */
26624     click: function(evt) {
26625         return this.handle(evt) ? !this.stopClick : true;
26626     },
26627         
26628     /**
26629      * Method: mousemove
26630      * Handle mouse moves.  Call the "over" callback if moving in to a feature,
26631      *     or the "out" callback if moving out of a feature.
26632      * 
26633      * Parameters:
26634      * evt - {Event} 
26635      *
26636      * Returns:
26637      * {Boolean}
26638      */
26639     mousemove: function(evt) {
26640         if (!this.callbacks['over'] && !this.callbacks['out']) {
26641             return true;
26642         }     
26643         this.handle(evt);
26644         return true;
26645     },
26646     
26647     /**
26648      * Method: dblclick
26649      * Handle dblclick.  Call the "dblclick" callback if dblclick on a feature.
26650      *
26651      * Parameters:
26652      * evt - {Event} 
26653      *
26654      * Returns:
26655      * {Boolean}
26656      */
26657     dblclick: function(evt) {
26658         return !this.handle(evt);
26659     },
26660
26661     /**
26662      * Method: geometryTypeMatches
26663      * Return true if the geometry type of the passed feature matches
26664      *     one of the geometry types in the geometryTypes array.
26665      *
26666      * Parameters:
26667      * feature - {<OpenLayers.Vector.Feature>}
26668      *
26669      * Returns:
26670      * {Boolean}
26671      */
26672     geometryTypeMatches: function(feature) {
26673         return this.geometryTypes == null ||
26674             OpenLayers.Util.indexOf(this.geometryTypes,
26675                                     feature.geometry.CLASS_NAME) > -1;
26676     },
26677
26678     /**
26679      * Method: handle
26680      *
26681      * Parameters:
26682      * evt - {Event}
26683      *
26684      * Returns:
26685      * {Boolean} The event occurred over a relevant feature.
26686      */
26687     handle: function(evt) {
26688         if(this.feature && !this.feature.layer) {
26689             // feature has been destroyed
26690             this.feature = null;
26691         }
26692         var type = evt.type;
26693         var handled = false;
26694         var previouslyIn = !!(this.feature); // previously in a feature
26695         var click = (type == "click" || type == "dblclick" || type == "touchstart");
26696         this.feature = this.layer.getFeatureFromEvent(evt);
26697         if(this.feature && !this.feature.layer) {
26698             // feature has been destroyed
26699             this.feature = null;
26700         }
26701         if(this.lastFeature && !this.lastFeature.layer) {
26702             // last feature has been destroyed
26703             this.lastFeature = null;
26704         }
26705         if(this.feature) {
26706             if(type === "touchstart") {
26707                 // stop the event to prevent Android Webkit from
26708                 // "flashing" the map div
26709                 OpenLayers.Event.preventDefault(evt);
26710             }
26711             var inNew = (this.feature != this.lastFeature);
26712             if(this.geometryTypeMatches(this.feature)) {
26713                 // in to a feature
26714                 if(previouslyIn && inNew) {
26715                     // out of last feature and in to another
26716                     if(this.lastFeature) {
26717                         this.triggerCallback(type, 'out', [this.lastFeature]);
26718                     }
26719                     this.triggerCallback(type, 'in', [this.feature]);
26720                 } else if(!previouslyIn || click) {
26721                     // in feature for the first time
26722                     this.triggerCallback(type, 'in', [this.feature]);
26723                 }
26724                 this.lastFeature = this.feature;
26725                 handled = true;
26726             } else {
26727                 // not in to a feature
26728                 if(this.lastFeature && (previouslyIn && inNew || click)) {
26729                     // out of last feature for the first time
26730                     this.triggerCallback(type, 'out', [this.lastFeature]);
26731                 }
26732                 // next time the mouse goes in a feature whose geometry type
26733                 // doesn't match we don't want to call the 'out' callback
26734                 // again, so let's set this.feature to null so that
26735                 // previouslyIn will evaluate to false the next time
26736                 // we enter handle. Yes, a bit hackish...
26737                 this.feature = null;
26738             }
26739         } else if(this.lastFeature && (previouslyIn || click)) {
26740             this.triggerCallback(type, 'out', [this.lastFeature]);
26741         }
26742         return handled;
26743     },
26744     
26745     /**
26746      * Method: triggerCallback
26747      * Call the callback keyed in the event map with the supplied arguments.
26748      *     For click and clickout, the <clickTolerance> is checked first.
26749      *
26750      * Parameters:
26751      * type - {String}
26752      */
26753     triggerCallback: function(type, mode, args) {
26754         var key = this.EVENTMAP[type][mode];
26755         if(key) {
26756             if(type == 'click' && this.up && this.down) {
26757                 // for click/clickout, only trigger callback if tolerance is met
26758                 var dpx = Math.sqrt(
26759                     Math.pow(this.up.x - this.down.x, 2) +
26760                     Math.pow(this.up.y - this.down.y, 2)
26761                 );
26762                 if(dpx <= this.clickTolerance) {
26763                     this.callback(key, args);
26764                 }
26765                 // we're done with this set of events now: clear the cached
26766                 // positions so we can't trip over them later (this can occur
26767                 // if one of the up/down events gets eaten before it gets to us
26768                 // but we still get the click)
26769                 this.up = this.down = null;
26770             } else {
26771                 this.callback(key, args);
26772             }
26773         }
26774     },
26775
26776     /**
26777      * Method: activate 
26778      * Turn on the handler.  Returns false if the handler was already active.
26779      *
26780      * Returns:
26781      * {Boolean}
26782      */
26783     activate: function() {
26784         var activated = false;
26785         if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
26786             this.moveLayerToTop();
26787             this.map.events.on({
26788                 "removelayer": this.handleMapEvents,
26789                 "changelayer": this.handleMapEvents,
26790                 scope: this
26791             });
26792             activated = true;
26793         }
26794         return activated;
26795     },
26796     
26797     /**
26798      * Method: deactivate 
26799      * Turn off the handler.  Returns false if the handler was already active.
26800      *
26801      * Returns: 
26802      * {Boolean}
26803      */
26804     deactivate: function() {
26805         var deactivated = false;
26806         if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
26807             this.moveLayerBack();
26808             this.feature = null;
26809             this.lastFeature = null;
26810             this.down = null;
26811             this.up = null;
26812             this.map.events.un({
26813                 "removelayer": this.handleMapEvents,
26814                 "changelayer": this.handleMapEvents,
26815                 scope: this
26816             });
26817             deactivated = true;
26818         }
26819         return deactivated;
26820     },
26821     
26822     /**
26823      * Method: handleMapEvents
26824      * 
26825      * Parameters:
26826      * evt - {Object}
26827      */
26828     handleMapEvents: function(evt) {
26829         if (evt.type == "removelayer" || evt.property == "order") {
26830             this.moveLayerToTop();
26831         }
26832     },
26833     
26834     /**
26835      * Method: moveLayerToTop
26836      * Moves the layer for this handler to the top, so mouse events can reach
26837      * it.
26838      */
26839     moveLayerToTop: function() {
26840         var index = Math.max(this.map.Z_INDEX_BASE['Feature'] - 1,
26841             this.layer.getZIndex()) + 1;
26842         this.layer.setZIndex(index);
26843         
26844     },
26845     
26846     /**
26847      * Method: moveLayerBack
26848      * Moves the layer back to the position determined by the map's layers
26849      * array.
26850      */
26851     moveLayerBack: function() {
26852         var index = this.layer.getZIndex() - 1;
26853         if (index >= this.map.Z_INDEX_BASE['Feature']) {
26854             this.layer.setZIndex(index);
26855         } else {
26856             this.map.setLayerZIndex(this.layer,
26857                 this.map.getLayerIndex(this.layer));
26858         }
26859     },
26860
26861     CLASS_NAME: "OpenLayers.Handler.Feature"
26862 });
26863 /* ======================================================================
26864     OpenLayers/Layer.js
26865    ====================================================================== */
26866
26867 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
26868  * full list of contributors). Published under the 2-clause BSD license.
26869  * See license.txt in the OpenLayers distribution or repository for the
26870  * full text of the license. */
26871
26872
26873 /**
26874  * @requires OpenLayers/BaseTypes/Class.js
26875  * @requires OpenLayers/Map.js
26876  * @requires OpenLayers/Projection.js
26877  */
26878
26879 /**
26880  * Class: OpenLayers.Layer
26881  */
26882 OpenLayers.Layer = OpenLayers.Class({
26883
26884     /**
26885      * APIProperty: id
26886      * {String}
26887      */
26888     id: null,
26889
26890     /** 
26891      * APIProperty: name
26892      * {String}
26893      */
26894     name: null,
26895
26896     /** 
26897      * APIProperty: div
26898      * {DOMElement}
26899      */
26900     div: null,
26901
26902     /**
26903      * APIProperty: opacity
26904      * {Float} The layer's opacity. Float number between 0.0 and 1.0. Default
26905      * is 1.
26906      */
26907     opacity: 1,
26908
26909     /**
26910      * APIProperty: alwaysInRange
26911      * {Boolean} If a layer's display should not be scale-based, this should 
26912      *     be set to true. This will cause the layer, as an overlay, to always 
26913      *     be 'active', by always returning true from the calculateInRange() 
26914      *     function. 
26915      * 
26916      *     If not explicitly specified for a layer, its value will be 
26917      *     determined on startup in initResolutions() based on whether or not 
26918      *     any scale-specific properties have been set as options on the 
26919      *     layer. If no scale-specific options have been set on the layer, we 
26920      *     assume that it should always be in range.
26921      * 
26922      *     See #987 for more info.
26923      */
26924     alwaysInRange: null,   
26925
26926     /**
26927      * Constant: RESOLUTION_PROPERTIES
26928      * {Array} The properties that are used for calculating resolutions
26929      *     information.
26930      */
26931     RESOLUTION_PROPERTIES: [
26932         'scales', 'resolutions',
26933         'maxScale', 'minScale',
26934         'maxResolution', 'minResolution',
26935         'numZoomLevels', 'maxZoomLevel'
26936     ],
26937
26938     /**
26939      * APIProperty: events
26940      * {<OpenLayers.Events>}
26941      *
26942      * Register a listener for a particular event with the following syntax:
26943      * (code)
26944      * layer.events.register(type, obj, listener);
26945      * (end)
26946      *
26947      * Listeners will be called with a reference to an event object.  The
26948      *     properties of this event depends on exactly what happened.
26949      *
26950      * All event objects have at least the following properties:
26951      * object - {Object} A reference to layer.events.object.
26952      * element - {DOMElement} A reference to layer.events.element.
26953      *
26954      * Supported map event types:
26955      * loadstart - Triggered when layer loading starts.  When using a Vector 
26956      *     layer with a Fixed or BBOX strategy, the event object includes 
26957      *     a *filter* property holding the OpenLayers.Filter used when 
26958      *     calling read on the protocol.
26959      * loadend - Triggered when layer loading ends.  When using a Vector layer
26960      *     with a Fixed or BBOX strategy, the event object includes a 
26961      *     *response* property holding an OpenLayers.Protocol.Response object.
26962      * visibilitychanged - Triggered when the layer's visibility property is
26963      *     changed, e.g. by turning the layer on or off in the layer switcher.
26964      *     Note that the actual visibility of the layer can also change if it
26965      *     gets out of range (see <calculateInRange>). If you also want to catch
26966      *     these cases, register for the map's 'changelayer' event instead.
26967      * move - Triggered when layer moves (triggered with every mousemove
26968      *     during a drag).
26969      * moveend - Triggered when layer is done moving, object passed as
26970      *     argument has a zoomChanged boolean property which tells that the
26971      *     zoom has changed.
26972      * added - Triggered after the layer is added to a map.  Listeners will
26973      *     receive an object with a *map* property referencing the map and a
26974      *     *layer* property referencing the layer.
26975      * removed - Triggered after the layer is removed from the map.  Listeners
26976      *     will receive an object with a *map* property referencing the map and
26977      *     a *layer* property referencing the layer.
26978      */
26979     events: null,
26980
26981     /**
26982      * APIProperty: map
26983      * {<OpenLayers.Map>} This variable is set when the layer is added to 
26984      *     the map, via the accessor function setMap().
26985      */
26986     map: null,
26987     
26988     /**
26989      * APIProperty: isBaseLayer
26990      * {Boolean} Whether or not the layer is a base layer. This should be set 
26991      *     individually by all subclasses. Default is false
26992      */
26993     isBaseLayer: false,
26994  
26995     /**
26996      * Property: alpha
26997      * {Boolean} The layer's images have an alpha channel.  Default is false.
26998      */
26999     alpha: false,
27000
27001     /** 
27002      * APIProperty: displayInLayerSwitcher
27003      * {Boolean} Display the layer's name in the layer switcher.  Default is
27004      *     true.
27005      */
27006     displayInLayerSwitcher: true,
27007
27008     /**
27009      * APIProperty: visibility
27010      * {Boolean} The layer should be displayed in the map.  Default is true.
27011      */
27012     visibility: true,
27013
27014     /**
27015      * APIProperty: attribution
27016      * {<Object>} or {<String>} Attribution information, displayed when an
27017      *     <OpenLayers.Control.Attribution> has been added to the map.
27018      *
27019      *     An object is required to store the full attribution information
27020      *     from a WMS capabilities response. Example attribution object:
27021      *     {title:"",href:"",logo:{format:"",width:10,height:10,href:""}}
27022      */
27023     attribution: null,
27024
27025     /** 
27026      * Property: inRange
27027      * {Boolean} The current map resolution is within the layer's min/max 
27028      *     range. This is set in <OpenLayers.Map.setCenter> whenever the zoom 
27029      *     changes.
27030      */
27031     inRange: false,
27032     
27033     /**
27034      * Propery: imageSize
27035      * {<OpenLayers.Size>} For layers with a gutter, the image is larger than 
27036      *     the tile by twice the gutter in each dimension.
27037      */
27038     imageSize: null,
27039     
27040   // OPTIONS
27041
27042     /** 
27043      * Property: options
27044      * {Object} An optional object whose properties will be set on the layer.
27045      *     Any of the layer properties can be set as a property of the options
27046      *     object and sent to the constructor when the layer is created.
27047      */
27048     options: null,
27049
27050     /**
27051      * APIProperty: eventListeners
27052      * {Object} If set as an option at construction, the eventListeners
27053      *     object will be registered with <OpenLayers.Events.on>.  Object
27054      *     structure must be a listeners object as shown in the example for
27055      *     the events.on method.
27056      */
27057     eventListeners: null,
27058
27059     /**
27060      * APIProperty: gutter
27061      * {Integer} Determines the width (in pixels) of the gutter around image
27062      *     tiles to ignore.  By setting this property to a non-zero value,
27063      *     images will be requested that are wider and taller than the tile
27064      *     size by a value of 2 x gutter.  This allows artifacts of rendering
27065      *     at tile edges to be ignored.  Set a gutter value that is equal to
27066      *     half the size of the widest symbol that needs to be displayed.
27067      *     Defaults to zero.  Non-tiled layers always have zero gutter.
27068      */ 
27069     gutter: 0, 
27070
27071     /**
27072      * APIProperty: projection
27073      * {<OpenLayers.Projection>} or {<String>} Specifies the projection of the layer.
27074      *     Can be set in the layer options. If not specified in the layer options,
27075      *     it is set to the default projection specified in the map,
27076      *     when the layer is added to the map.
27077      *     Projection along with default maxExtent and resolutions
27078      *     are set automatically with commercial baselayers in EPSG:3857,
27079      *     such as Google, Bing and OpenStreetMap, and do not need to be specified.
27080      *     Otherwise, if specifying projection, also set maxExtent,
27081      *     maxResolution or resolutions as appropriate.
27082      *     When using vector layers with strategies, layer projection should be set
27083      *     to the projection of the source data if that is different from the map default.
27084      * 
27085      *     Can be either a string or an <OpenLayers.Projection> object;
27086      *     if a string is passed, will be converted to an object when
27087      *     the layer is added to the map.
27088      * 
27089      */
27090     projection: null,    
27091     
27092     /**
27093      * APIProperty: units
27094      * {String} The layer map units.  Defaults to null.  Possible values
27095      *     are 'degrees' (or 'dd'), 'm', 'ft', 'km', 'mi', 'inches'.
27096      *     Normally taken from the projection.
27097      *     Only required if both map and layers do not define a projection,
27098      *     or if they define a projection which does not define units.
27099      */
27100     units: null,
27101
27102     /**
27103      * APIProperty: scales
27104      * {Array}  An array of map scales in descending order.  The values in the
27105      *     array correspond to the map scale denominator.  Note that these
27106      *     values only make sense if the display (monitor) resolution of the
27107      *     client is correctly guessed by whomever is configuring the
27108      *     application.  In addition, the units property must also be set.
27109      *     Use <resolutions> instead wherever possible.
27110      */
27111     scales: null,
27112
27113     /**
27114      * APIProperty: resolutions
27115      * {Array} A list of map resolutions (map units per pixel) in descending
27116      *     order.  If this is not set in the layer constructor, it will be set
27117      *     based on other resolution related properties (maxExtent,
27118      *     maxResolution, maxScale, etc.).
27119      */
27120     resolutions: null,
27121     
27122     /**
27123      * APIProperty: maxExtent
27124      * {<OpenLayers.Bounds>|Array} If provided as an array, the array
27125      *     should consist of four values (left, bottom, right, top).
27126      *     The maximum extent for the layer.  Defaults to null.
27127      * 
27128      *     The center of these bounds will not stray outside
27129      *     of the viewport extent during panning.  In addition, if
27130      *     <displayOutsideMaxExtent> is set to false, data will not be
27131      *     requested that falls completely outside of these bounds.
27132      */
27133     maxExtent: null,
27134     
27135     /**
27136      * APIProperty: minExtent
27137      * {<OpenLayers.Bounds>|Array} If provided as an array, the array
27138      *     should consist of four values (left, bottom, right, top).
27139      *     The minimum extent for the layer.  Defaults to null.
27140      */
27141     minExtent: null,
27142     
27143     /**
27144      * APIProperty: maxResolution
27145      * {Float} Default max is 360 deg / 256 px, which corresponds to
27146      *     zoom level 0 on gmaps.  Specify a different value in the layer 
27147      *     options if you are not using the default <OpenLayers.Map.tileSize>
27148      *     and displaying the whole world.
27149      */
27150     maxResolution: null,
27151
27152     /**
27153      * APIProperty: minResolution
27154      * {Float}
27155      */
27156     minResolution: null,
27157
27158     /**
27159      * APIProperty: numZoomLevels
27160      * {Integer}
27161      */
27162     numZoomLevels: null,
27163     
27164     /**
27165      * APIProperty: minScale
27166      * {Float}
27167      */
27168     minScale: null,
27169     
27170     /**
27171      * APIProperty: maxScale
27172      * {Float}
27173      */
27174     maxScale: null,
27175
27176     /**
27177      * APIProperty: displayOutsideMaxExtent
27178      * {Boolean} Request map tiles that are completely outside of the max 
27179      *     extent for this layer. Defaults to false.
27180      */
27181     displayOutsideMaxExtent: false,
27182
27183     /**
27184      * APIProperty: wrapDateLine
27185      * {Boolean} Wraps the world at the international dateline, so the map can
27186      * be panned infinitely in longitudinal direction. Only use this on the
27187      * base layer, and only if the layer's maxExtent equals the world bounds.
27188      * #487 for more info.   
27189      */
27190     wrapDateLine: false,
27191     
27192     /**
27193      * Property: metadata
27194      * {Object} This object can be used to store additional information on a
27195      *     layer object.
27196      */
27197     metadata: null,
27198     
27199     /**
27200      * Constructor: OpenLayers.Layer
27201      *
27202      * Parameters:
27203      * name - {String} The layer name
27204      * options - {Object} Hashtable of extra options to tag onto the layer
27205      */
27206     initialize: function(name, options) {
27207
27208         this.metadata = {};
27209         
27210         options = OpenLayers.Util.extend({}, options);
27211         // make sure we respect alwaysInRange if set on the prototype
27212         if (this.alwaysInRange != null) {
27213             options.alwaysInRange = this.alwaysInRange;
27214         }
27215         this.addOptions(options);
27216
27217         this.name = name;
27218         
27219         if (this.id == null) {
27220
27221             this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
27222
27223             this.div = OpenLayers.Util.createDiv(this.id);
27224             this.div.style.width = "100%";
27225             this.div.style.height = "100%";
27226             this.div.dir = "ltr";
27227
27228             this.events = new OpenLayers.Events(this, this.div);
27229             if(this.eventListeners instanceof Object) {
27230                 this.events.on(this.eventListeners);
27231             }
27232
27233         }
27234     },
27235     
27236     /**
27237      * Method: destroy
27238      * Destroy is a destructor: this is to alleviate cyclic references which
27239      *     the Javascript garbage cleaner can not take care of on its own.
27240      *
27241      * Parameters:
27242      * setNewBaseLayer - {Boolean} Set a new base layer when this layer has
27243      *     been destroyed.  Default is true.
27244      */
27245     destroy: function(setNewBaseLayer) {
27246         if (setNewBaseLayer == null) {
27247             setNewBaseLayer = true;
27248         }
27249         if (this.map != null) {
27250             this.map.removeLayer(this, setNewBaseLayer);
27251         }
27252         this.projection = null;
27253         this.map = null;
27254         this.name = null;
27255         this.div = null;
27256         this.options = null;
27257
27258         if (this.events) {
27259             if(this.eventListeners) {
27260                 this.events.un(this.eventListeners);
27261             }
27262             this.events.destroy();
27263         }
27264         this.eventListeners = null;
27265         this.events = null;
27266     },
27267     
27268    /**
27269     * Method: clone
27270     *
27271     * Parameters:
27272     * obj - {<OpenLayers.Layer>} The layer to be cloned
27273     *
27274     * Returns:
27275     * {<OpenLayers.Layer>} An exact clone of this <OpenLayers.Layer>
27276     */
27277     clone: function (obj) {
27278         
27279         if (obj == null) {
27280             obj = new OpenLayers.Layer(this.name, this.getOptions());
27281         }
27282         
27283         // catch any randomly tagged-on properties
27284         OpenLayers.Util.applyDefaults(obj, this);
27285         
27286         // a cloned layer should never have its map property set
27287         //  because it has not been added to a map yet. 
27288         obj.map = null;
27289         
27290         return obj;
27291     },
27292     
27293     /**
27294      * Method: getOptions
27295      * Extracts an object from the layer with the properties that were set as
27296      *     options, but updates them with the values currently set on the
27297      *     instance.
27298      * 
27299      * Returns:
27300      * {Object} the <options> of the layer, representing the current state.
27301      */
27302     getOptions: function() {
27303         var options = {};
27304         for(var o in this.options) {
27305             options[o] = this[o];
27306         }
27307         return options;
27308     },
27309     
27310     /** 
27311      * APIMethod: setName
27312      * Sets the new layer name for this layer.  Can trigger a changelayer event
27313      *     on the map.
27314      *
27315      * Parameters:
27316      * newName - {String} The new name.
27317      */
27318     setName: function(newName) {
27319         if (newName != this.name) {
27320             this.name = newName;
27321             if (this.map != null) {
27322                 this.map.events.triggerEvent("changelayer", {
27323                     layer: this,
27324                     property: "name"
27325                 });
27326             }
27327         }
27328     },    
27329     
27330    /**
27331     * APIMethod: addOptions
27332     * 
27333     * Parameters:
27334     * newOptions - {Object}
27335     * reinitialize - {Boolean} If set to true, and if resolution options of the
27336     *     current baseLayer were changed, the map will be recentered to make
27337     *     sure that it is displayed with a valid resolution, and a
27338     *     changebaselayer event will be triggered.
27339     */
27340     addOptions: function (newOptions, reinitialize) {
27341
27342         if (this.options == null) {
27343             this.options = {};
27344         }
27345         
27346         if (newOptions) {
27347             // make sure this.projection references a projection object
27348             if(typeof newOptions.projection == "string") {
27349                 newOptions.projection = new OpenLayers.Projection(newOptions.projection);
27350             }
27351             if (newOptions.projection) {
27352                 // get maxResolution, units and maxExtent from projection defaults if
27353                 // they are not defined already
27354                 OpenLayers.Util.applyDefaults(newOptions,
27355                     OpenLayers.Projection.defaults[newOptions.projection.getCode()]);
27356             }
27357             // allow array for extents
27358             if (newOptions.maxExtent && !(newOptions.maxExtent instanceof OpenLayers.Bounds)) {
27359                 newOptions.maxExtent = new OpenLayers.Bounds(newOptions.maxExtent);
27360             }
27361             if (newOptions.minExtent && !(newOptions.minExtent instanceof OpenLayers.Bounds)) {
27362                 newOptions.minExtent = new OpenLayers.Bounds(newOptions.minExtent);
27363             }
27364         }
27365
27366         // update our copy for clone
27367         OpenLayers.Util.extend(this.options, newOptions);
27368
27369         // add new options to this
27370         OpenLayers.Util.extend(this, newOptions);
27371         
27372         // get the units from the projection, if we have a projection
27373         // and it it has units
27374         if(this.projection && this.projection.getUnits()) {
27375             this.units = this.projection.getUnits();
27376         }
27377
27378         // re-initialize resolutions if necessary, i.e. if any of the
27379         // properties of the "properties" array defined below is set
27380         // in the new options
27381         if(this.map) {
27382             // store current resolution so we can try to restore it later
27383             var resolution = this.map.getResolution();
27384             var properties = this.RESOLUTION_PROPERTIES.concat(
27385                 ["projection", "units", "minExtent", "maxExtent"]
27386             );
27387             for(var o in newOptions) {
27388                 if(newOptions.hasOwnProperty(o) &&
27389                    OpenLayers.Util.indexOf(properties, o) >= 0) {
27390
27391                     this.initResolutions();
27392                     if (reinitialize && this.map.baseLayer === this) {
27393                         // update map position, and restore previous resolution
27394                         this.map.setCenter(this.map.getCenter(),
27395                             this.map.getZoomForResolution(resolution),
27396                             false, true
27397                         );
27398                         // trigger a changebaselayer event to make sure that
27399                         // all controls (especially
27400                         // OpenLayers.Control.PanZoomBar) get notified of the
27401                         // new options
27402                         this.map.events.triggerEvent("changebaselayer", {
27403                             layer: this
27404                         });
27405                     }
27406                     break;
27407                 }
27408             }
27409         }
27410     },
27411
27412     /**
27413      * APIMethod: onMapResize
27414      * This function can be implemented by subclasses
27415      */
27416     onMapResize: function() {
27417         //this function can be implemented by subclasses  
27418     },
27419
27420     /**
27421      * APIMethod: redraw
27422      * Redraws the layer.  Returns true if the layer was redrawn, false if not.
27423      *
27424      * Returns:
27425      * {Boolean} The layer was redrawn.
27426      */
27427     redraw: function() {
27428         var redrawn = false;
27429         if (this.map) {
27430
27431             // min/max Range may have changed
27432             this.inRange = this.calculateInRange();
27433
27434             // map's center might not yet be set
27435             var extent = this.getExtent();
27436
27437             if (extent && this.inRange && this.visibility) {
27438                 var zoomChanged = true;
27439                 this.moveTo(extent, zoomChanged, false);
27440                 this.events.triggerEvent("moveend",
27441                     {"zoomChanged": zoomChanged});
27442                 redrawn = true;
27443             }
27444         }
27445         return redrawn;
27446     },
27447
27448     /**
27449      * Method: moveTo
27450      * 
27451      * Parameters:
27452      * bounds - {<OpenLayers.Bounds>}
27453      * zoomChanged - {Boolean} Tells when zoom has changed, as layers have to
27454      *     do some init work in that case.
27455      * dragging - {Boolean}
27456      */
27457     moveTo:function(bounds, zoomChanged, dragging) {
27458         var display = this.visibility;
27459         if (!this.isBaseLayer) {
27460             display = display && this.inRange;
27461         }
27462         this.display(display);
27463     },
27464
27465     /**
27466      * Method: moveByPx
27467      * Move the layer based on pixel vector. To be implemented by subclasses.
27468      *
27469      * Parameters:
27470      * dx - {Number} The x coord of the displacement vector.
27471      * dy - {Number} The y coord of the displacement vector.
27472      */
27473     moveByPx: function(dx, dy) {
27474     },
27475
27476     /**
27477      * Method: setMap
27478      * Set the map property for the layer. This is done through an accessor
27479      *     so that subclasses can override this and take special action once 
27480      *     they have their map variable set. 
27481      * 
27482      *     Here we take care to bring over any of the necessary default 
27483      *     properties from the map. 
27484      * 
27485      * Parameters:
27486      * map - {<OpenLayers.Map>}
27487      */
27488     setMap: function(map) {
27489         if (this.map == null) {
27490         
27491             this.map = map;
27492             
27493             // grab some essential layer data from the map if it hasn't already
27494             //  been set
27495             this.maxExtent = this.maxExtent || this.map.maxExtent;
27496             this.minExtent = this.minExtent || this.map.minExtent;
27497
27498             this.projection = this.projection || this.map.projection;
27499             if (typeof this.projection == "string") {
27500                 this.projection = new OpenLayers.Projection(this.projection);
27501             }
27502
27503             // Check the projection to see if we can get units -- if not, refer
27504             // to properties.
27505             if (this.projection && this.projection.getUnits()) {
27506                 this.units = this.projection.getUnits();
27507             }
27508             else {
27509                 this.units = this.units || this.map.units;
27510             }
27511             
27512             this.initResolutions();
27513             
27514             if (!this.isBaseLayer) {
27515                 this.inRange = this.calculateInRange();
27516                 var show = ((this.visibility) && (this.inRange));
27517                 this.div.style.display = show ? "" : "none";
27518             }
27519             
27520             // deal with gutters
27521             this.setTileSize();
27522         }
27523     },
27524     
27525     /**
27526      * Method: afterAdd
27527      * Called at the end of the map.addLayer sequence.  At this point, the map
27528      *     will have a base layer.  To be overridden by subclasses.
27529      */
27530     afterAdd: function() {
27531     },
27532     
27533     /**
27534      * APIMethod: removeMap
27535      * Just as setMap() allows each layer the possibility to take a 
27536      *     personalized action on being added to the map, removeMap() allows
27537      *     each layer to take a personalized action on being removed from it. 
27538      *     For now, this will be mostly unused, except for the EventPane layer,
27539      *     which needs this hook so that it can remove the special invisible
27540      *     pane. 
27541      * 
27542      * Parameters:
27543      * map - {<OpenLayers.Map>}
27544      */
27545     removeMap: function(map) {
27546         //to be overridden by subclasses
27547     },
27548     
27549     /**
27550      * APIMethod: getImageSize
27551      *
27552      * Parameters:
27553      * bounds - {<OpenLayers.Bounds>} optional tile bounds, can be used
27554      *     by subclasses that have to deal with different tile sizes at the
27555      *     layer extent edges (e.g. Zoomify)
27556      * 
27557      * Returns:
27558      * {<OpenLayers.Size>} The size that the image should be, taking into 
27559      *     account gutters.
27560      */ 
27561     getImageSize: function(bounds) { 
27562         return (this.imageSize || this.tileSize); 
27563     },    
27564   
27565     /**
27566      * APIMethod: setTileSize
27567      * Set the tile size based on the map size.  This also sets layer.imageSize
27568      *     or use by Tile.Image.
27569      * 
27570      * Parameters:
27571      * size - {<OpenLayers.Size>}
27572      */
27573     setTileSize: function(size) {
27574         var tileSize = (size) ? size :
27575                                 ((this.tileSize) ? this.tileSize :
27576                                                    this.map.getTileSize());
27577         this.tileSize = tileSize;
27578         if(this.gutter) {
27579           // layers with gutters need non-null tile sizes
27580           //if(tileSize == null) {
27581           //    OpenLayers.console.error("Error in layer.setMap() for " +
27582           //                              this.name + ": layers with " +
27583           //                              "gutters need non-null tile sizes");
27584           //}
27585             this.imageSize = new OpenLayers.Size(tileSize.w + (2*this.gutter), 
27586                                                  tileSize.h + (2*this.gutter)); 
27587         }
27588     },
27589
27590     /**
27591      * APIMethod: getVisibility
27592      * 
27593      * Returns:
27594      * {Boolean} The layer should be displayed (if in range).
27595      */
27596     getVisibility: function() {
27597         return this.visibility;
27598     },
27599
27600     /** 
27601      * APIMethod: setVisibility
27602      * Set the visibility flag for the layer and hide/show & redraw 
27603      *     accordingly. Fire event unless otherwise specified
27604      * 
27605      * Note that visibility is no longer simply whether or not the layer's
27606      *     style.display is set to "block". Now we store a 'visibility' state 
27607      *     property on the layer class, this allows us to remember whether or 
27608      *     not we *desire* for a layer to be visible. In the case where the 
27609      *     map's resolution is out of the layer's range, this desire may be 
27610      *     subverted.
27611      * 
27612      * Parameters:
27613      * visibility - {Boolean} Whether or not to display the layer (if in range)
27614      */
27615     setVisibility: function(visibility) {
27616         if (visibility != this.visibility) {
27617             this.visibility = visibility;
27618             this.display(visibility);
27619             this.redraw();
27620             if (this.map != null) {
27621                 this.map.events.triggerEvent("changelayer", {
27622                     layer: this,
27623                     property: "visibility"
27624                 });
27625             }
27626             this.events.triggerEvent("visibilitychanged");
27627         }
27628     },
27629
27630     /** 
27631      * APIMethod: display
27632      * Hide or show the Layer. This is designed to be used internally, and 
27633      *     is not generally the way to enable or disable the layer. For that,
27634      *     use the setVisibility function instead..
27635      * 
27636      * Parameters:
27637      * display - {Boolean}
27638      */
27639     display: function(display) {
27640         if (display != (this.div.style.display != "none")) {
27641             this.div.style.display = (display && this.calculateInRange()) ? "block" : "none";
27642         }
27643     },
27644
27645     /**
27646      * APIMethod: calculateInRange
27647      * 
27648      * Returns:
27649      * {Boolean} The layer is displayable at the current map's current
27650      *     resolution. Note that if 'alwaysInRange' is true for the layer, 
27651      *     this function will always return true.
27652      */
27653     calculateInRange: function() {
27654         var inRange = false;
27655
27656         if (this.alwaysInRange) {
27657             inRange = true;
27658         } else {
27659             if (this.map) {
27660                 var resolution = this.map.getResolution();
27661                 inRange = ( (resolution >= this.minResolution) &&
27662                             (resolution <= this.maxResolution) );
27663             }
27664         }
27665         return inRange;
27666     },
27667
27668     /** 
27669      * APIMethod: setIsBaseLayer
27670      * 
27671      * Parameters:
27672      * isBaseLayer - {Boolean}
27673      */
27674     setIsBaseLayer: function(isBaseLayer) {
27675         if (isBaseLayer != this.isBaseLayer) {
27676             this.isBaseLayer = isBaseLayer;
27677             if (this.map != null) {
27678                 this.map.events.triggerEvent("changebaselayer", {
27679                     layer: this
27680                 });
27681             }
27682         }
27683     },
27684
27685   /********************************************************/
27686   /*                                                      */
27687   /*                 Baselayer Functions                  */
27688   /*                                                      */
27689   /********************************************************/
27690   
27691     /** 
27692      * Method: initResolutions
27693      * This method's responsibility is to set up the 'resolutions' array 
27694      *     for the layer -- this array is what the layer will use to interface
27695      *     between the zoom levels of the map and the resolution display 
27696      *     of the layer.
27697      * 
27698      * The user has several options that determine how the array is set up.
27699      *  
27700      * For a detailed explanation, see the following wiki from the 
27701      *     openlayers.org homepage:
27702      *     http://trac.openlayers.org/wiki/SettingZoomLevels
27703      */
27704     initResolutions: function() {
27705
27706         // ok we want resolutions, here's our strategy:
27707         //
27708         // 1. if resolutions are defined in the layer config, use them
27709         // 2. else, if scales are defined in the layer config then derive
27710         //    resolutions from these scales
27711         // 3. else, attempt to calculate resolutions from maxResolution,
27712         //    minResolution, numZoomLevels, maxZoomLevel set in the
27713         //    layer config
27714         // 4. if we still don't have resolutions, and if resolutions
27715         //    are defined in the same, use them
27716         // 5. else, if scales are defined in the map then derive
27717         //    resolutions from these scales
27718         // 6. else, attempt to calculate resolutions from maxResolution,
27719         //    minResolution, numZoomLevels, maxZoomLevel set in the
27720         //    map
27721         // 7. hope for the best!
27722
27723         var i, len, p;
27724         var props = {}, alwaysInRange = true;
27725
27726         // get resolution data from layer config
27727         // (we also set alwaysInRange in the layer as appropriate)
27728         for(i=0, len=this.RESOLUTION_PROPERTIES.length; i<len; i++) {
27729             p = this.RESOLUTION_PROPERTIES[i];
27730             props[p] = this.options[p];
27731             if(alwaysInRange && this.options[p]) {
27732                 alwaysInRange = false;
27733             }
27734         }
27735         if(this.options.alwaysInRange == null) {
27736             this.alwaysInRange = alwaysInRange;
27737         }
27738
27739         // if we don't have resolutions then attempt to derive them from scales
27740         if(props.resolutions == null) {
27741             props.resolutions = this.resolutionsFromScales(props.scales);
27742         }
27743
27744         // if we still don't have resolutions then attempt to calculate them
27745         if(props.resolutions == null) {
27746             props.resolutions = this.calculateResolutions(props);
27747         }
27748
27749         // if we couldn't calculate resolutions then we look at we have
27750         // in the map
27751         if(props.resolutions == null) {
27752             for(i=0, len=this.RESOLUTION_PROPERTIES.length; i<len; i++) {
27753                 p = this.RESOLUTION_PROPERTIES[i];
27754                 props[p] = this.options[p] != null ?
27755                     this.options[p] : this.map[p];
27756             }
27757             if(props.resolutions == null) {
27758                 props.resolutions = this.resolutionsFromScales(props.scales);
27759             }
27760             if(props.resolutions == null) {
27761                 props.resolutions = this.calculateResolutions(props);
27762             }
27763         }
27764
27765         // ok, we new need to set properties in the instance
27766
27767         // get maxResolution from the config if it's defined there
27768         var maxResolution;
27769         if(this.options.maxResolution &&
27770            this.options.maxResolution !== "auto") {
27771             maxResolution = this.options.maxResolution;
27772         }
27773         if(this.options.minScale) {
27774             maxResolution = OpenLayers.Util.getResolutionFromScale(
27775                 this.options.minScale, this.units);
27776         }
27777
27778         // get minResolution from the config if it's defined there
27779         var minResolution;
27780         if(this.options.minResolution &&
27781            this.options.minResolution !== "auto") {
27782             minResolution = this.options.minResolution;
27783         }
27784         if(this.options.maxScale) {
27785             minResolution = OpenLayers.Util.getResolutionFromScale(
27786                 this.options.maxScale, this.units);
27787         }
27788
27789         if(props.resolutions) {
27790
27791             //sort resolutions array descendingly
27792             props.resolutions.sort(function(a, b) {
27793                 return (b - a);
27794             });
27795
27796             // if we still don't have a maxResolution get it from the
27797             // resolutions array
27798             if(!maxResolution) {
27799                 maxResolution = props.resolutions[0];
27800             }
27801
27802             // if we still don't have a minResolution get it from the
27803             // resolutions array
27804             if(!minResolution) {
27805                 var lastIdx = props.resolutions.length - 1;
27806                 minResolution = props.resolutions[lastIdx];
27807             }
27808         }
27809
27810         this.resolutions = props.resolutions;
27811         if(this.resolutions) {
27812             len = this.resolutions.length;
27813             this.scales = new Array(len);
27814             for(i=0; i<len; i++) {
27815                 this.scales[i] = OpenLayers.Util.getScaleFromResolution(
27816                     this.resolutions[i], this.units);
27817             }
27818             this.numZoomLevels = len;
27819         }
27820         this.minResolution = minResolution;
27821         if(minResolution) {
27822             this.maxScale = OpenLayers.Util.getScaleFromResolution(
27823                 minResolution, this.units);
27824         }
27825         this.maxResolution = maxResolution;
27826         if(maxResolution) {
27827             this.minScale = OpenLayers.Util.getScaleFromResolution(
27828                 maxResolution, this.units);
27829         }
27830     },
27831
27832     /**
27833      * Method: resolutionsFromScales
27834      * Derive resolutions from scales.
27835      *
27836      * Parameters:
27837      * scales - {Array(Number)} Scales
27838      *
27839      * Returns
27840      * {Array(Number)} Resolutions
27841      */
27842     resolutionsFromScales: function(scales) {
27843         if(scales == null) {
27844             return;
27845         }
27846         var resolutions, i, len;
27847         len = scales.length;
27848         resolutions = new Array(len);
27849         for(i=0; i<len; i++) {
27850             resolutions[i] = OpenLayers.Util.getResolutionFromScale(
27851                 scales[i], this.units);
27852         }
27853         return resolutions;
27854     },
27855
27856     /**
27857      * Method: calculateResolutions
27858      * Calculate resolutions based on the provided properties.
27859      *
27860      * Parameters:
27861      * props - {Object} Properties
27862      *
27863      * Returns:
27864      * {Array({Number})} Array of resolutions.
27865      */
27866     calculateResolutions: function(props) {
27867
27868         var viewSize, wRes, hRes;
27869
27870         // determine maxResolution
27871         var maxResolution = props.maxResolution;
27872         if(props.minScale != null) {
27873             maxResolution =
27874                 OpenLayers.Util.getResolutionFromScale(props.minScale,
27875                                                        this.units);
27876         } else if(maxResolution == "auto" && this.maxExtent != null) {
27877             viewSize = this.map.getSize();
27878             wRes = this.maxExtent.getWidth() / viewSize.w;
27879             hRes = this.maxExtent.getHeight() / viewSize.h;
27880             maxResolution = Math.max(wRes, hRes);
27881         }
27882
27883         // determine minResolution
27884         var minResolution = props.minResolution;
27885         if(props.maxScale != null) {
27886             minResolution =
27887                 OpenLayers.Util.getResolutionFromScale(props.maxScale,
27888                                                        this.units);
27889         } else if(props.minResolution == "auto" && this.minExtent != null) {
27890             viewSize = this.map.getSize();
27891             wRes = this.minExtent.getWidth() / viewSize.w;
27892             hRes = this.minExtent.getHeight()/ viewSize.h;
27893             minResolution = Math.max(wRes, hRes);
27894         }
27895
27896         if(typeof maxResolution !== "number" &&
27897            typeof minResolution !== "number" &&
27898            this.maxExtent != null) {
27899             // maxResolution for default grid sets assumes that at zoom
27900             // level zero, the whole world fits on one tile.
27901             var tileSize = this.map.getTileSize();
27902             maxResolution = Math.max(
27903                 this.maxExtent.getWidth() / tileSize.w,
27904                 this.maxExtent.getHeight() / tileSize.h
27905             );
27906         }
27907
27908         // determine numZoomLevels
27909         var maxZoomLevel = props.maxZoomLevel;
27910         var numZoomLevels = props.numZoomLevels;
27911         if(typeof minResolution === "number" &&
27912            typeof maxResolution === "number" && numZoomLevels === undefined) {
27913             var ratio = maxResolution / minResolution;
27914             numZoomLevels = Math.floor(Math.log(ratio) / Math.log(2)) + 1;
27915         } else if(numZoomLevels === undefined && maxZoomLevel != null) {
27916             numZoomLevels = maxZoomLevel + 1;
27917         }
27918
27919         // are we able to calculate resolutions?
27920         if(typeof numZoomLevels !== "number" || numZoomLevels <= 0 ||
27921            (typeof maxResolution !== "number" &&
27922                 typeof minResolution !== "number")) {
27923             return;
27924         }
27925
27926         // now we have numZoomLevels and at least one of maxResolution
27927         // or minResolution, we can populate the resolutions array
27928
27929         var resolutions = new Array(numZoomLevels);
27930         var base = 2;
27931         if(typeof minResolution == "number" &&
27932            typeof maxResolution == "number") {
27933             // if maxResolution and minResolution are set, we calculate
27934             // the base for exponential scaling that starts at
27935             // maxResolution and ends at minResolution in numZoomLevels
27936             // steps.
27937             base = Math.pow(
27938                     (maxResolution / minResolution),
27939                 (1 / (numZoomLevels - 1))
27940             );
27941         }
27942
27943         var i;
27944         if(typeof maxResolution === "number") {
27945             for(i=0; i<numZoomLevels; i++) {
27946                 resolutions[i] = maxResolution / Math.pow(base, i);
27947             }
27948         } else {
27949             for(i=0; i<numZoomLevels; i++) {
27950                 resolutions[numZoomLevels - 1 - i] =
27951                     minResolution * Math.pow(base, i);
27952             }
27953         }
27954
27955         return resolutions;
27956     },
27957
27958     /**
27959      * APIMethod: getResolution
27960      * 
27961      * Returns:
27962      * {Float} The currently selected resolution of the map, taken from the
27963      *     resolutions array, indexed by current zoom level.
27964      */
27965     getResolution: function() {
27966         var zoom = this.map.getZoom();
27967         return this.getResolutionForZoom(zoom);
27968     },
27969
27970     /** 
27971      * APIMethod: getExtent
27972      * 
27973      * Returns:
27974      * {<OpenLayers.Bounds>} A Bounds object which represents the lon/lat 
27975      *     bounds of the current viewPort.
27976      */
27977     getExtent: function() {
27978         // just use stock map calculateBounds function -- passing no arguments
27979         //  means it will user map's current center & resolution
27980         //
27981         return this.map.calculateBounds();
27982     },
27983
27984     /**
27985      * APIMethod: getZoomForExtent
27986      * 
27987      * Parameters:
27988      * extent - {<OpenLayers.Bounds>}
27989      * closest - {Boolean} Find the zoom level that most closely fits the 
27990      *     specified bounds. Note that this may result in a zoom that does 
27991      *     not exactly contain the entire extent.
27992      *     Default is false.
27993      *
27994      * Returns:
27995      * {Integer} The index of the zoomLevel (entry in the resolutions array) 
27996      *     for the passed-in extent. We do this by calculating the ideal 
27997      *     resolution for the given extent (based on the map size) and then 
27998      *     calling getZoomForResolution(), passing along the 'closest'
27999      *     parameter.
28000      */
28001     getZoomForExtent: function(extent, closest) {
28002         var viewSize = this.map.getSize();
28003         var idealResolution = Math.max( extent.getWidth()  / viewSize.w,
28004                                         extent.getHeight() / viewSize.h );
28005
28006         return this.getZoomForResolution(idealResolution, closest);
28007     },
28008     
28009     /** 
28010      * Method: getDataExtent
28011      * Calculates the max extent which includes all of the data for the layer.
28012      *     This function is to be implemented by subclasses.
28013      * 
28014      * Returns:
28015      * {<OpenLayers.Bounds>}
28016      */
28017     getDataExtent: function () {
28018         //to be implemented by subclasses
28019     },
28020
28021     /**
28022      * APIMethod: getResolutionForZoom
28023      * 
28024      * Parameters:
28025      * zoom - {Float}
28026      * 
28027      * Returns:
28028      * {Float} A suitable resolution for the specified zoom.
28029      */
28030     getResolutionForZoom: function(zoom) {
28031         zoom = Math.max(0, Math.min(zoom, this.resolutions.length - 1));
28032         var resolution;
28033         if(this.map.fractionalZoom) {
28034             var low = Math.floor(zoom);
28035             var high = Math.ceil(zoom);
28036             resolution = this.resolutions[low] -
28037                 ((zoom-low) * (this.resolutions[low]-this.resolutions[high]));
28038         } else {
28039             resolution = this.resolutions[Math.round(zoom)];
28040         }
28041         return resolution;
28042     },
28043
28044     /**
28045      * APIMethod: getZoomForResolution
28046      * 
28047      * Parameters:
28048      * resolution - {Float}
28049      * closest - {Boolean} Find the zoom level that corresponds to the absolute 
28050      *     closest resolution, which may result in a zoom whose corresponding
28051      *     resolution is actually smaller than we would have desired (if this
28052      *     is being called from a getZoomForExtent() call, then this means that
28053      *     the returned zoom index might not actually contain the entire 
28054      *     extent specified... but it'll be close).
28055      *     Default is false.
28056      * 
28057      * Returns:
28058      * {Integer} The index of the zoomLevel (entry in the resolutions array) 
28059      *     that corresponds to the best fit resolution given the passed in 
28060      *     value and the 'closest' specification.
28061      */
28062     getZoomForResolution: function(resolution, closest) {
28063         var zoom, i, len;
28064         if(this.map.fractionalZoom) {
28065             var lowZoom = 0;
28066             var highZoom = this.resolutions.length - 1;
28067             var highRes = this.resolutions[lowZoom];
28068             var lowRes = this.resolutions[highZoom];
28069             var res;
28070             for(i=0, len=this.resolutions.length; i<len; ++i) {
28071                 res = this.resolutions[i];
28072                 if(res >= resolution) {
28073                     highRes = res;
28074                     lowZoom = i;
28075                 }
28076                 if(res <= resolution) {
28077                     lowRes = res;
28078                     highZoom = i;
28079                     break;
28080                 }
28081             }
28082             var dRes = highRes - lowRes;
28083             if(dRes > 0) {
28084                 zoom = lowZoom + ((highRes - resolution) / dRes);
28085             } else {
28086                 zoom = lowZoom;
28087             }
28088         } else {
28089             var diff;
28090             var minDiff = Number.POSITIVE_INFINITY;
28091             for(i=0, len=this.resolutions.length; i<len; i++) {            
28092                 if (closest) {
28093                     diff = Math.abs(this.resolutions[i] - resolution);
28094                     if (diff > minDiff) {
28095                         break;
28096                     }
28097                     minDiff = diff;
28098                 } else {
28099                     if (this.resolutions[i] < resolution) {
28100                         break;
28101                     }
28102                 }
28103             }
28104             zoom = Math.max(0, i-1);
28105         }
28106         return zoom;
28107     },
28108     
28109     /**
28110      * APIMethod: getLonLatFromViewPortPx
28111      * 
28112      * Parameters:
28113      * viewPortPx - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or
28114      *                                          an object with a 'x'
28115      *                                          and 'y' properties.
28116      *
28117      * Returns:
28118      * {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in 
28119      *     view port <OpenLayers.Pixel>, translated into lon/lat by the layer.
28120      */
28121     getLonLatFromViewPortPx: function (viewPortPx) {
28122         var lonlat = null;
28123         var map = this.map;
28124         if (viewPortPx != null && map.minPx) {
28125             var res = map.getResolution();
28126             var maxExtent = map.getMaxExtent({restricted: true});
28127             var lon = (viewPortPx.x - map.minPx.x) * res + maxExtent.left;
28128             var lat = (map.minPx.y - viewPortPx.y) * res + maxExtent.top;
28129             lonlat = new OpenLayers.LonLat(lon, lat);
28130
28131             if (this.wrapDateLine) {
28132                 lonlat = lonlat.wrapDateLine(this.maxExtent);
28133             }
28134         }
28135         return lonlat;
28136     },
28137
28138     /**
28139      * APIMethod: getViewPortPxFromLonLat
28140      * Returns a pixel location given a map location.  This method will return
28141      *     fractional pixel values.
28142      * 
28143      * Parameters:
28144      * lonlat - {<OpenLayers.LonLat>|Object} An OpenLayers.LonLat or
28145      *                                       an object with a 'lon'
28146      *                                       and 'lat' properties.
28147      *
28148      * Returns: 
28149      * {<OpenLayers.Pixel>} An <OpenLayers.Pixel> which is the passed-in 
28150      *     lonlat translated into view port pixels.
28151      */
28152     getViewPortPxFromLonLat: function (lonlat, resolution) {
28153         var px = null; 
28154         if (lonlat != null) {
28155             resolution = resolution || this.map.getResolution();
28156             var extent = this.map.calculateBounds(null, resolution);
28157             px = new OpenLayers.Pixel(
28158                 (1/resolution * (lonlat.lon - extent.left)),
28159                 (1/resolution * (extent.top - lonlat.lat))
28160             );    
28161         }
28162         return px;
28163     },
28164     
28165     /**
28166      * APIMethod: setOpacity
28167      * Sets the opacity for the entire layer (all images)
28168      * 
28169      * Parameters:
28170      * opacity - {Float}
28171      */
28172     setOpacity: function(opacity) {
28173         if (opacity != this.opacity) {
28174             this.opacity = opacity;
28175             var childNodes = this.div.childNodes;
28176             for(var i = 0, len = childNodes.length; i < len; ++i) {
28177                 var element = childNodes[i].firstChild || childNodes[i];
28178                 var lastChild = childNodes[i].lastChild;
28179                 //TODO de-uglify this
28180                 if (lastChild && lastChild.nodeName.toLowerCase() === "iframe") {
28181                     element = lastChild.parentNode;
28182                 }
28183                 OpenLayers.Util.modifyDOMElement(element, null, null, null, 
28184                                                  null, null, null, opacity);
28185             }
28186             if (this.map != null) {
28187                 this.map.events.triggerEvent("changelayer", {
28188                     layer: this,
28189                     property: "opacity"
28190                 });
28191             }
28192         }
28193     },
28194
28195     /**
28196      * Method: getZIndex
28197      * 
28198      * Returns: 
28199      * {Integer} the z-index of this layer
28200      */    
28201     getZIndex: function () {
28202         return this.div.style.zIndex;
28203     },
28204
28205     /**
28206      * Method: setZIndex
28207      * 
28208      * Parameters: 
28209      * zIndex - {Integer}
28210      */    
28211     setZIndex: function (zIndex) {
28212         this.div.style.zIndex = zIndex;
28213     },
28214
28215     /**
28216      * Method: adjustBounds
28217      * This function will take a bounds, and if wrapDateLine option is set
28218      *     on the layer, it will return a bounds which is wrapped around the 
28219      *     world. We do not wrap for bounds which *cross* the 
28220      *     maxExtent.left/right, only bounds which are entirely to the left 
28221      *     or entirely to the right.
28222      * 
28223      * Parameters:
28224      * bounds - {<OpenLayers.Bounds>}
28225      */
28226     adjustBounds: function (bounds) {
28227
28228         if (this.gutter) {
28229             // Adjust the extent of a bounds in map units by the 
28230             // layer's gutter in pixels.
28231             var mapGutter = this.gutter * this.map.getResolution();
28232             bounds = new OpenLayers.Bounds(bounds.left - mapGutter,
28233                                            bounds.bottom - mapGutter,
28234                                            bounds.right + mapGutter,
28235                                            bounds.top + mapGutter);
28236         }
28237
28238         if (this.wrapDateLine) {
28239             // wrap around the date line, within the limits of rounding error
28240             var wrappingOptions = { 
28241                 'rightTolerance':this.getResolution(),
28242                 'leftTolerance':this.getResolution()
28243             };    
28244             bounds = bounds.wrapDateLine(this.maxExtent, wrappingOptions);
28245                               
28246         }
28247         return bounds;
28248     },
28249
28250     CLASS_NAME: "OpenLayers.Layer"
28251 });
28252 /* ======================================================================
28253     OpenLayers/Renderer.js
28254    ====================================================================== */
28255
28256 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
28257  * full list of contributors). Published under the 2-clause BSD license.
28258  * See license.txt in the OpenLayers distribution or repository for the
28259  * full text of the license. */
28260
28261 /**
28262  * @requires OpenLayers/BaseTypes/Class.js
28263  */
28264
28265 /**
28266  * Class: OpenLayers.Renderer 
28267  * This is the base class for all renderers.
28268  *
28269  * This is based on a merger code written by Paul Spencer and Bertil Chapuis.
28270  * It is largely composed of virtual functions that are to be implemented
28271  * in technology-specific subclasses, but there is some generic code too.
28272  * 
28273  * The functions that *are* implemented here merely deal with the maintenance
28274  *  of the size and extent variables, as well as the cached 'resolution' 
28275  *  value. 
28276  * 
28277  * A note to the user that all subclasses should use getResolution() instead
28278  *  of directly accessing this.resolution in order to correctly use the 
28279  *  cacheing system.
28280  *
28281  */
28282 OpenLayers.Renderer = OpenLayers.Class({
28283
28284     /** 
28285      * Property: container
28286      * {DOMElement} 
28287      */
28288     container: null,
28289     
28290     /**
28291      * Property: root
28292      * {DOMElement}
28293      */
28294     root: null,
28295
28296     /** 
28297      * Property: extent
28298      * {<OpenLayers.Bounds>}
28299      */
28300     extent: null,
28301
28302     /**
28303      * Property: locked
28304      * {Boolean} If the renderer is currently in a state where many things
28305      *     are changing, the 'locked' property is set to true. This means 
28306      *     that renderers can expect at least one more drawFeature event to be
28307      *     called with the 'locked' property set to 'true': In some renderers,
28308      *     this might make sense to use as a 'only update local information'
28309      *     flag. 
28310      */  
28311     locked: false,
28312     
28313     /** 
28314      * Property: size
28315      * {<OpenLayers.Size>} 
28316      */
28317     size: null,
28318     
28319     /**
28320      * Property: resolution
28321      * {Float} cache of current map resolution
28322      */
28323     resolution: null,
28324     
28325     /**
28326      * Property: map  
28327      * {<OpenLayers.Map>} Reference to the map -- this is set in Vector's setMap()
28328      */
28329     map: null,
28330     
28331     /**
28332      * Property: featureDx
28333      * {Number} Feature offset in x direction. Will be calculated for and
28334      * applied to the current feature while rendering (see
28335      * <calculateFeatureDx>).
28336      */
28337     featureDx: 0,
28338     
28339     /**
28340      * Constructor: OpenLayers.Renderer 
28341      *
28342      * Parameters:
28343      * containerID - {<String>} 
28344      * options - {Object} options for this renderer. See sublcasses for
28345      *     supported options.
28346      */
28347     initialize: function(containerID, options) {
28348         this.container = OpenLayers.Util.getElement(containerID);
28349         OpenLayers.Util.extend(this, options);
28350     },
28351     
28352     /**
28353      * APIMethod: destroy
28354      */
28355     destroy: function() {
28356         this.container = null;
28357         this.extent = null;
28358         this.size =  null;
28359         this.resolution = null;
28360         this.map = null;
28361     },
28362
28363     /**
28364      * APIMethod: supported
28365      * This should be overridden by specific subclasses
28366      * 
28367      * Returns:
28368      * {Boolean} Whether or not the browser supports the renderer class
28369      */
28370     supported: function() {
28371         return false;
28372     },    
28373     
28374     /**
28375      * Method: setExtent
28376      * Set the visible part of the layer.
28377      *
28378      * Resolution has probably changed, so we nullify the resolution 
28379      * cache (this.resolution) -- this way it will be re-computed when 
28380      * next it is needed.
28381      * We nullify the resolution cache (this.resolution) if resolutionChanged
28382      * is set to true - this way it will be re-computed on the next
28383      * getResolution() request.
28384      *
28385      * Parameters:
28386      * extent - {<OpenLayers.Bounds>}
28387      * resolutionChanged - {Boolean}
28388      *
28389      * Returns:
28390      * {Boolean} true to notify the layer that the new extent does not exceed
28391      *     the coordinate range, and the features will not need to be redrawn.
28392      *     False otherwise.
28393      */
28394     setExtent: function(extent, resolutionChanged) {
28395         this.extent = extent.clone();
28396         if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
28397             var ratio = extent.getWidth() / this.map.getExtent().getWidth(),
28398                 extent = extent.scale(1 / ratio);
28399             this.extent = extent.wrapDateLine(this.map.getMaxExtent()).scale(ratio);
28400         }
28401         if (resolutionChanged) {
28402             this.resolution = null;
28403         }
28404         return true;
28405     },
28406     
28407     /**
28408      * Method: setSize
28409      * Sets the size of the drawing surface.
28410      * 
28411      * Resolution has probably changed, so we nullify the resolution 
28412      * cache (this.resolution) -- this way it will be re-computed when 
28413      * next it is needed.
28414      *
28415      * Parameters:
28416      * size - {<OpenLayers.Size>} 
28417      */
28418     setSize: function(size) {
28419         this.size = size.clone();
28420         this.resolution = null;
28421     },
28422     
28423     /** 
28424      * Method: getResolution
28425      * Uses cached copy of resolution if available to minimize computing
28426      * 
28427      * Returns:
28428      * {Float} The current map's resolution
28429      */
28430     getResolution: function() {
28431         this.resolution = this.resolution || this.map.getResolution();
28432         return this.resolution;
28433     },
28434     
28435     /**
28436      * Method: drawFeature
28437      * Draw the feature.  The optional style argument can be used
28438      * to override the feature's own style.  This method should only
28439      * be called from layer.drawFeature().
28440      *
28441      * Parameters:
28442      * feature - {<OpenLayers.Feature.Vector>} 
28443      * style - {<Object>}
28444      * 
28445      * Returns:
28446      * {Boolean} true if the feature has been drawn completely, false if not,
28447      *     undefined if the feature had no geometry
28448      */
28449     drawFeature: function(feature, style) {
28450         if(style == null) {
28451             style = feature.style;
28452         }
28453         if (feature.geometry) {
28454             var bounds = feature.geometry.getBounds();
28455             if(bounds) {
28456                 var worldBounds;
28457                 if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
28458                     worldBounds = this.map.getMaxExtent();
28459                 }
28460                 if (!bounds.intersectsBounds(this.extent, {worldBounds: worldBounds})) {
28461                     style = {display: "none"};
28462                 } else {
28463                     this.calculateFeatureDx(bounds, worldBounds);
28464                 }
28465                 var rendered = this.drawGeometry(feature.geometry, style, feature.id);
28466                 if(style.display != "none" && style.label && rendered !== false) {
28467
28468                     var location = feature.geometry.getCentroid(); 
28469                     if(style.labelXOffset || style.labelYOffset) {
28470                         var xOffset = isNaN(style.labelXOffset) ? 0 : style.labelXOffset;
28471                         var yOffset = isNaN(style.labelYOffset) ? 0 : style.labelYOffset;
28472                         var res = this.getResolution();
28473                         location.move(xOffset*res, yOffset*res);
28474                     }
28475                     this.drawText(feature.id, style, location);
28476                 } else {
28477                     this.removeText(feature.id);
28478                 }
28479                 return rendered;
28480             }
28481         }
28482     },
28483
28484     /**
28485      * Method: calculateFeatureDx
28486      * {Number} Calculates the feature offset in x direction. Looking at the
28487      * center of the feature bounds and the renderer extent, we calculate how
28488      * many world widths the two are away from each other. This distance is
28489      * used to shift the feature as close as possible to the center of the
28490      * current enderer extent, which ensures that the feature is visible in the
28491      * current viewport.
28492      *
28493      * Parameters:
28494      * bounds - {<OpenLayers.Bounds>} Bounds of the feature
28495      * worldBounds - {<OpenLayers.Bounds>} Bounds of the world
28496      */
28497     calculateFeatureDx: function(bounds, worldBounds) {
28498         this.featureDx = 0;
28499         if (worldBounds) {
28500             var worldWidth = worldBounds.getWidth(),
28501                 rendererCenterX = (this.extent.left + this.extent.right) / 2,
28502                 featureCenterX = (bounds.left + bounds.right) / 2,
28503                 worldsAway = Math.round((featureCenterX - rendererCenterX) / worldWidth);
28504             this.featureDx = worldsAway * worldWidth;
28505         }
28506     },
28507
28508     /** 
28509      * Method: drawGeometry
28510      * 
28511      * Draw a geometry.  This should only be called from the renderer itself.
28512      * Use layer.drawFeature() from outside the renderer.
28513      * virtual function
28514      *
28515      * Parameters:
28516      * geometry - {<OpenLayers.Geometry>} 
28517      * style - {Object} 
28518      * featureId - {<String>} 
28519      */
28520     drawGeometry: function(geometry, style, featureId) {},
28521         
28522     /**
28523      * Method: drawText
28524      * Function for drawing text labels.
28525      * This method is only called by the renderer itself.
28526      * 
28527      * Parameters: 
28528      * featureId - {String}
28529      * style -
28530      * location - {<OpenLayers.Geometry.Point>}
28531      */
28532     drawText: function(featureId, style, location) {},
28533
28534     /**
28535      * Method: removeText
28536      * Function for removing text labels.
28537      * This method is only called by the renderer itself.
28538      * 
28539      * Parameters: 
28540      * featureId - {String}
28541      */
28542     removeText: function(featureId) {},
28543     
28544     /**
28545      * Method: clear
28546      * Clear all vectors from the renderer.
28547      * virtual function.
28548      */    
28549     clear: function() {},
28550
28551     /**
28552      * Method: getFeatureIdFromEvent
28553      * Returns a feature id from an event on the renderer.  
28554      * How this happens is specific to the renderer.  This should be
28555      * called from layer.getFeatureFromEvent().
28556      * Virtual function.
28557      * 
28558      * Parameters:
28559      * evt - {<OpenLayers.Event>} 
28560      *
28561      * Returns:
28562      * {String} A feature id or undefined.
28563      */
28564     getFeatureIdFromEvent: function(evt) {},
28565     
28566     /**
28567      * Method: eraseFeatures 
28568      * This is called by the layer to erase features
28569      * 
28570      * Parameters:
28571      * features - {Array(<OpenLayers.Feature.Vector>)} 
28572      */
28573     eraseFeatures: function(features) {
28574         if(!(OpenLayers.Util.isArray(features))) {
28575             features = [features];
28576         }
28577         for(var i=0, len=features.length; i<len; ++i) {
28578             var feature = features[i];
28579             this.eraseGeometry(feature.geometry, feature.id);
28580             this.removeText(feature.id);
28581         }
28582     },
28583     
28584     /**
28585      * Method: eraseGeometry
28586      * Remove a geometry from the renderer (by id).
28587      * virtual function.
28588      * 
28589      * Parameters:
28590      * geometry - {<OpenLayers.Geometry>} 
28591      * featureId - {String}
28592      */
28593     eraseGeometry: function(geometry, featureId) {},
28594     
28595     /**
28596      * Method: moveRoot
28597      * moves this renderer's root to a (different) renderer.
28598      * To be implemented by subclasses that require a common renderer root for
28599      * feature selection.
28600      * 
28601      * Parameters:
28602      * renderer - {<OpenLayers.Renderer>} target renderer for the moved root
28603      */
28604     moveRoot: function(renderer) {},
28605
28606     /**
28607      * Method: getRenderLayerId
28608      * Gets the layer that this renderer's output appears on. If moveRoot was
28609      * used, this will be different from the id of the layer containing the
28610      * features rendered by this renderer.
28611      * 
28612      * Returns:
28613      * {String} the id of the output layer.
28614      */
28615     getRenderLayerId: function() {
28616         return this.container.id;
28617     },
28618     
28619     /**
28620      * Method: applyDefaultSymbolizer
28621      * 
28622      * Parameters:
28623      * symbolizer - {Object}
28624      * 
28625      * Returns:
28626      * {Object}
28627      */
28628     applyDefaultSymbolizer: function(symbolizer) {
28629         var result = OpenLayers.Util.extend({},
28630             OpenLayers.Renderer.defaultSymbolizer);
28631         if(symbolizer.stroke === false) {
28632             delete result.strokeWidth;
28633             delete result.strokeColor;
28634         }
28635         if(symbolizer.fill === false) {
28636             delete result.fillColor;
28637         }
28638         OpenLayers.Util.extend(result, symbolizer);
28639         return result;
28640     },
28641
28642     CLASS_NAME: "OpenLayers.Renderer"
28643 });
28644
28645 /**
28646  * Constant: OpenLayers.Renderer.defaultSymbolizer
28647  * {Object} Properties from this symbolizer will be applied to symbolizers
28648  *     with missing properties. This can also be used to set a global
28649  *     symbolizer default in OpenLayers. To be SLD 1.x compliant, add the
28650  *     following code before rendering any vector features:
28651  * (code)
28652  * OpenLayers.Renderer.defaultSymbolizer = {
28653  *     fillColor: "#808080",
28654  *     fillOpacity: 1,
28655  *     strokeColor: "#000000",
28656  *     strokeOpacity: 1,
28657  *     strokeWidth: 1,
28658  *     pointRadius: 3,
28659  *     graphicName: "square"
28660  * };
28661  * (end)
28662  */
28663 OpenLayers.Renderer.defaultSymbolizer = {
28664     fillColor: "#000000",
28665     strokeColor: "#000000",
28666     strokeWidth: 2,
28667     fillOpacity: 1,
28668     strokeOpacity: 1,
28669     pointRadius: 0,
28670     labelAlign: 'cm'
28671 };
28672     
28673
28674
28675 /**
28676  * Constant: OpenLayers.Renderer.symbol
28677  * Coordinate arrays for well known (named) symbols.
28678  */
28679 OpenLayers.Renderer.symbol = {
28680     "star": [350,75, 379,161, 469,161, 397,215, 423,301, 350,250, 277,301,
28681             303,215, 231,161, 321,161, 350,75],
28682     "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,
28683             4,0],
28684     "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],
28685     "square": [0,0, 0,1, 1,1, 1,0, 0,0],
28686     "triangle": [0,10, 10,10, 5,0, 0,10]
28687 };
28688 /* ======================================================================
28689     OpenLayers/Layer/Vector.js
28690    ====================================================================== */
28691
28692 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
28693  * full list of contributors). Published under the 2-clause BSD license.
28694  * See license.txt in the OpenLayers distribution or repository for the
28695  * full text of the license. */
28696
28697 /**
28698  * @requires OpenLayers/Layer.js
28699  * @requires OpenLayers/Renderer.js
28700  * @requires OpenLayers/StyleMap.js
28701  * @requires OpenLayers/Feature/Vector.js
28702  * @requires OpenLayers/Console.js
28703  * @requires OpenLayers/Lang.js
28704  */
28705
28706 /**
28707  * Class: OpenLayers.Layer.Vector
28708  * Instances of OpenLayers.Layer.Vector are used to render vector data from
28709  *     a variety of sources. Create a new vector layer with the
28710  *     <OpenLayers.Layer.Vector> constructor.
28711  *
28712  * Inherits from:
28713  *  - <OpenLayers.Layer>
28714  */
28715 OpenLayers.Layer.Vector = OpenLayers.Class(OpenLayers.Layer, {
28716
28717     /**
28718      * APIProperty: events
28719      * {<OpenLayers.Events>}
28720      *
28721      * Register a listener for a particular event with the following syntax:
28722      * (code)
28723      * layer.events.register(type, obj, listener);
28724      * (end)
28725      *
28726      * Listeners will be called with a reference to an event object.  The
28727      *     properties of this event depends on exactly what happened.
28728      *
28729      * All event objects have at least the following properties:
28730      * object - {Object} A reference to layer.events.object.
28731      * element - {DOMElement} A reference to layer.events.element.
28732      *
28733      * Supported map event types (in addition to those from <OpenLayers.Layer.events>):
28734      * beforefeatureadded - Triggered before a feature is added.  Listeners
28735      *      will receive an object with a *feature* property referencing the
28736      *      feature to be added.  To stop the feature from being added, a
28737      *      listener should return false.
28738      * beforefeaturesadded - Triggered before an array of features is added.
28739      *      Listeners will receive an object with a *features* property
28740      *      referencing the feature to be added. To stop the features from
28741      *      being added, a listener should return false.
28742      * featureadded - Triggered after a feature is added.  The event
28743      *      object passed to listeners will have a *feature* property with a
28744      *      reference to the added feature.
28745      * featuresadded - Triggered after features are added.  The event
28746      *      object passed to listeners will have a *features* property with a
28747      *      reference to an array of added features.
28748      * beforefeatureremoved - Triggered before a feature is removed. Listeners
28749      *      will receive an object with a *feature* property referencing the
28750      *      feature to be removed.
28751      * beforefeaturesremoved - Triggered before multiple features are removed. 
28752      *      Listeners will receive an object with a *features* property
28753      *      referencing the features to be removed.
28754      * featureremoved - Triggered after a feature is removed. The event
28755      *      object passed to listeners will have a *feature* property with a
28756      *      reference to the removed feature.
28757      * featuresremoved - Triggered after features are removed. The event
28758      *      object passed to listeners will have a *features* property with a
28759      *      reference to an array of removed features.
28760      * beforefeatureselected - Triggered before a feature is selected.  Listeners
28761      *      will receive an object with a *feature* property referencing the
28762      *      feature to be selected. To stop the feature from being selectd, a
28763      *      listener should return false.
28764      * featureselected - Triggered after a feature is selected.  Listeners
28765      *      will receive an object with a *feature* property referencing the
28766      *      selected feature.
28767      * featureunselected - Triggered after a feature is unselected.
28768      *      Listeners will receive an object with a *feature* property
28769      *      referencing the unselected feature.
28770      * beforefeaturemodified - Triggered when a feature is selected to 
28771      *      be modified.  Listeners will receive an object with a *feature* 
28772      *      property referencing the selected feature.
28773      * featuremodified - Triggered when a feature has been modified.
28774      *      Listeners will receive an object with a *feature* property referencing 
28775      *      the modified feature.
28776      * afterfeaturemodified - Triggered when a feature is finished being modified.
28777      *      Listeners will receive an object with a *feature* property referencing 
28778      *      the modified feature.
28779      * vertexmodified - Triggered when a vertex within any feature geometry
28780      *      has been modified.  Listeners will receive an object with a
28781      *      *feature* property referencing the modified feature, a *vertex*
28782      *      property referencing the vertex modified (always a point geometry),
28783      *      and a *pixel* property referencing the pixel location of the
28784      *      modification.
28785      * vertexremoved - Triggered when a vertex within any feature geometry
28786      *      has been deleted.  Listeners will receive an object with a
28787      *      *feature* property referencing the modified feature, a *vertex*
28788      *      property referencing the vertex modified (always a point geometry),
28789      *      and a *pixel* property referencing the pixel location of the
28790      *      removal.
28791      * sketchstarted - Triggered when a feature sketch bound for this layer
28792      *      is started.  Listeners will receive an object with a *feature*
28793      *      property referencing the new sketch feature and a *vertex* property
28794      *      referencing the creation point.
28795      * sketchmodified - Triggered when a feature sketch bound for this layer
28796      *      is modified.  Listeners will receive an object with a *vertex*
28797      *      property referencing the modified vertex and a *feature* property
28798      *      referencing the sketch feature.
28799      * sketchcomplete - Triggered when a feature sketch bound for this layer
28800      *      is complete.  Listeners will receive an object with a *feature*
28801      *      property referencing the sketch feature.  By returning false, a
28802      *      listener can stop the sketch feature from being added to the layer.
28803      * refresh - Triggered when something wants a strategy to ask the protocol
28804      *      for a new set of features.
28805      */
28806
28807     /**
28808      * APIProperty: isBaseLayer
28809      * {Boolean} The layer is a base layer.  Default is false.  Set this property
28810      * in the layer options.
28811      */
28812     isBaseLayer: false,
28813
28814     /** 
28815      * APIProperty: isFixed
28816      * {Boolean} Whether the layer remains in one place while dragging the
28817      * map. Note that setting this to true will move the layer to the bottom
28818      * of the layer stack.
28819      */
28820     isFixed: false,
28821
28822     /** 
28823      * APIProperty: features
28824      * {Array(<OpenLayers.Feature.Vector>)} 
28825      */
28826     features: null,
28827     
28828     /** 
28829      * Property: filter
28830      * {<OpenLayers.Filter>} The filter set in this layer,
28831      *     a strategy launching read requests can combined
28832      *     this filter with its own filter.
28833      */
28834     filter: null,
28835     
28836     /** 
28837      * Property: selectedFeatures
28838      * {Array(<OpenLayers.Feature.Vector>)} 
28839      */
28840     selectedFeatures: null,
28841     
28842     /**
28843      * Property: unrenderedFeatures
28844      * {Object} hash of features, keyed by feature.id, that the renderer
28845      *     failed to draw
28846      */
28847     unrenderedFeatures: null,
28848
28849     /**
28850      * APIProperty: reportError
28851      * {Boolean} report friendly error message when loading of renderer
28852      * fails.
28853      */
28854     reportError: true, 
28855
28856     /** 
28857      * APIProperty: style
28858      * {Object} Default style for the layer
28859      */
28860     style: null,
28861     
28862     /**
28863      * Property: styleMap
28864      * {<OpenLayers.StyleMap>}
28865      */
28866     styleMap: null,
28867     
28868     /**
28869      * Property: strategies
28870      * {Array(<OpenLayers.Strategy>})} Optional list of strategies for the layer.
28871      */
28872     strategies: null,
28873     
28874     /**
28875      * Property: protocol
28876      * {<OpenLayers.Protocol>} Optional protocol for the layer.
28877      */
28878     protocol: null,
28879     
28880     /**
28881      * Property: renderers
28882      * {Array(String)} List of supported Renderer classes. Add to this list to
28883      * add support for additional renderers. This list is ordered:
28884      * the first renderer which returns true for the  'supported()'
28885      * method will be used, if not defined in the 'renderer' option.
28886      */
28887     renderers: ['SVG', 'VML', 'Canvas'],
28888     
28889     /** 
28890      * Property: renderer
28891      * {<OpenLayers.Renderer>}
28892      */
28893     renderer: null,
28894     
28895     /**
28896      * APIProperty: rendererOptions
28897      * {Object} Options for the renderer. See {<OpenLayers.Renderer>} for
28898      *     supported options.
28899      */
28900     rendererOptions: null,
28901     
28902     /** 
28903      * APIProperty: geometryType
28904      * {String} geometryType allows you to limit the types of geometries this
28905      * layer supports. This should be set to something like
28906      * "OpenLayers.Geometry.Point" to limit types.
28907      */
28908     geometryType: null,
28909
28910     /** 
28911      * Property: drawn
28912      * {Boolean} Whether the Vector Layer features have been drawn yet.
28913      */
28914     drawn: false,
28915     
28916     /** 
28917      * APIProperty: ratio
28918      * {Float} This specifies the ratio of the size of the visiblity of the Vector Layer features to the size of the map.
28919      */   
28920     ratio: 1,
28921
28922     /**
28923      * Constructor: OpenLayers.Layer.Vector
28924      * Create a new vector layer
28925      *
28926      * Parameters:
28927      * name - {String} A name for the layer
28928      * options - {Object} Optional object with non-default properties to set on
28929      *           the layer.
28930      *
28931      * Returns:
28932      * {<OpenLayers.Layer.Vector>} A new vector layer
28933      */
28934     initialize: function(name, options) {
28935         OpenLayers.Layer.prototype.initialize.apply(this, arguments);
28936
28937         // allow user-set renderer, otherwise assign one
28938         if (!this.renderer || !this.renderer.supported()) {  
28939             this.assignRenderer();
28940         }
28941
28942         // if no valid renderer found, display error
28943         if (!this.renderer || !this.renderer.supported()) {
28944             this.renderer = null;
28945             this.displayError();
28946         } 
28947
28948         if (!this.styleMap) {
28949             this.styleMap = new OpenLayers.StyleMap();
28950         }
28951
28952         this.features = [];
28953         this.selectedFeatures = [];
28954         this.unrenderedFeatures = {};
28955         
28956         // Allow for custom layer behavior
28957         if(this.strategies){
28958             for(var i=0, len=this.strategies.length; i<len; i++) {
28959                 this.strategies[i].setLayer(this);
28960             }
28961         }
28962
28963     },
28964
28965     /**
28966      * APIMethod: destroy
28967      * Destroy this layer
28968      */
28969     destroy: function() {
28970         if (this.strategies) {
28971             var strategy, i, len;
28972             for(i=0, len=this.strategies.length; i<len; i++) {
28973                 strategy = this.strategies[i];
28974                 if(strategy.autoDestroy) {
28975                     strategy.destroy();
28976                 }
28977             }
28978             this.strategies = null;
28979         }
28980         if (this.protocol) {
28981             if(this.protocol.autoDestroy) {
28982                 this.protocol.destroy();
28983             }
28984             this.protocol = null;
28985         }
28986         this.destroyFeatures();
28987         this.features = null;
28988         this.selectedFeatures = null;
28989         this.unrenderedFeatures = null;
28990         if (this.renderer) {
28991             this.renderer.destroy();
28992         }
28993         this.renderer = null;
28994         this.geometryType = null;
28995         this.drawn = null;
28996         OpenLayers.Layer.prototype.destroy.apply(this, arguments);  
28997     },
28998
28999     /**
29000      * Method: clone
29001      * Create a clone of this layer.
29002      * 
29003      * Note: Features of the layer are also cloned.
29004      *
29005      * Returns:
29006      * {<OpenLayers.Layer.Vector>} An exact clone of this layer
29007      */
29008     clone: function (obj) {
29009         
29010         if (obj == null) {
29011             obj = new OpenLayers.Layer.Vector(this.name, this.getOptions());
29012         }
29013
29014         //get all additions from superclasses
29015         obj = OpenLayers.Layer.prototype.clone.apply(this, [obj]);
29016
29017         // copy/set any non-init, non-simple values here
29018         var features = this.features;
29019         var len = features.length;
29020         var clonedFeatures = new Array(len);
29021         for(var i=0; i<len; ++i) {
29022             clonedFeatures[i] = features[i].clone();
29023         }
29024         obj.features = clonedFeatures;
29025
29026         return obj;
29027     },    
29028     
29029     /**
29030      * Method: refresh
29031      * Ask the layer to request features again and redraw them.  Triggers
29032      *     the refresh event if the layer is in range and visible.
29033      *
29034      * Parameters:
29035      * obj - {Object} Optional object with properties for any listener of
29036      *     the refresh event.
29037      */
29038     refresh: function(obj) {
29039         if(this.calculateInRange() && this.visibility) {
29040             this.events.triggerEvent("refresh", obj);
29041         }
29042     },
29043
29044     /** 
29045      * Method: assignRenderer
29046      * Iterates through the available renderer implementations and selects 
29047      * and assigns the first one whose "supported()" function returns true.
29048      */    
29049     assignRenderer: function()  {
29050         for (var i=0, len=this.renderers.length; i<len; i++) {
29051             var rendererClass = this.renderers[i];
29052             var renderer = (typeof rendererClass == "function") ?
29053                 rendererClass :
29054                 OpenLayers.Renderer[rendererClass];
29055             if (renderer && renderer.prototype.supported()) {
29056                 this.renderer = new renderer(this.div, this.rendererOptions);
29057                 break;
29058             }  
29059         }  
29060     },
29061
29062     /** 
29063      * Method: displayError 
29064      * Let the user know their browser isn't supported.
29065      */
29066     displayError: function() {
29067         if (this.reportError) {
29068             OpenLayers.Console.userError(OpenLayers.i18n("browserNotSupported", 
29069                                      {renderers: this. renderers.join('\n')}));
29070         }    
29071     },
29072
29073     /** 
29074      * Method: setMap
29075      * The layer has been added to the map. 
29076      * 
29077      * If there is no renderer set, the layer can't be used. Remove it.
29078      * Otherwise, give the renderer a reference to the map and set its size.
29079      * 
29080      * Parameters:
29081      * map - {<OpenLayers.Map>} 
29082      */
29083     setMap: function(map) {        
29084         OpenLayers.Layer.prototype.setMap.apply(this, arguments);
29085
29086         if (!this.renderer) {
29087             this.map.removeLayer(this);
29088         } else {
29089             this.renderer.map = this.map;
29090
29091             var newSize = this.map.getSize();
29092             newSize.w = newSize.w * this.ratio;
29093             newSize.h = newSize.h * this.ratio;
29094             this.renderer.setSize(newSize);
29095         }
29096     },
29097
29098     /**
29099      * Method: afterAdd
29100      * Called at the end of the map.addLayer sequence.  At this point, the map
29101      *     will have a base layer.  Any autoActivate strategies will be
29102      *     activated here.
29103      */
29104     afterAdd: function() {
29105         if(this.strategies) {
29106             var strategy, i, len;
29107             for(i=0, len=this.strategies.length; i<len; i++) {
29108                 strategy = this.strategies[i];
29109                 if(strategy.autoActivate) {
29110                     strategy.activate();
29111                 }
29112             }
29113         }
29114     },
29115
29116     /**
29117      * Method: removeMap
29118      * The layer has been removed from the map.
29119      *
29120      * Parameters:
29121      * map - {<OpenLayers.Map>}
29122      */
29123     removeMap: function(map) {
29124         this.drawn = false;
29125         if(this.strategies) {
29126             var strategy, i, len;
29127             for(i=0, len=this.strategies.length; i<len; i++) {
29128                 strategy = this.strategies[i];
29129                 if(strategy.autoActivate) {
29130                     strategy.deactivate();
29131                 }
29132             }
29133         }
29134     },
29135     
29136     /**
29137      * Method: onMapResize
29138      * Notify the renderer of the change in size. 
29139      * 
29140      */
29141     onMapResize: function() {
29142         OpenLayers.Layer.prototype.onMapResize.apply(this, arguments);
29143         
29144         var newSize = this.map.getSize();
29145         newSize.w = newSize.w * this.ratio;
29146         newSize.h = newSize.h * this.ratio;
29147         this.renderer.setSize(newSize);
29148     },
29149
29150     /**
29151      * Method: moveTo
29152      *  Reset the vector layer's div so that it once again is lined up with 
29153      *   the map. Notify the renderer of the change of extent, and in the
29154      *   case of a change of zoom level (resolution), have the 
29155      *   renderer redraw features.
29156      * 
29157      *  If the layer has not yet been drawn, cycle through the layer's 
29158      *   features and draw each one.
29159      * 
29160      * Parameters:
29161      * bounds - {<OpenLayers.Bounds>} 
29162      * zoomChanged - {Boolean} 
29163      * dragging - {Boolean} 
29164      */
29165     moveTo: function(bounds, zoomChanged, dragging) {
29166         OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
29167         
29168         var coordSysUnchanged = true;
29169         if (!dragging) {
29170             this.renderer.root.style.visibility = 'hidden';
29171
29172             var viewSize = this.map.getSize(),
29173                 viewWidth = viewSize.w,
29174                 viewHeight = viewSize.h,
29175                 offsetLeft = (viewWidth / 2 * this.ratio) - viewWidth / 2,
29176                 offsetTop = (viewHeight / 2 * this.ratio) - viewHeight / 2;
29177             offsetLeft += this.map.layerContainerOriginPx.x;
29178             offsetLeft = -Math.round(offsetLeft);
29179             offsetTop += this.map.layerContainerOriginPx.y;
29180             offsetTop = -Math.round(offsetTop);
29181
29182             this.div.style.left = offsetLeft + 'px';
29183             this.div.style.top = offsetTop + 'px';
29184
29185             var extent = this.map.getExtent().scale(this.ratio);
29186             coordSysUnchanged = this.renderer.setExtent(extent, zoomChanged);
29187
29188             this.renderer.root.style.visibility = 'visible';
29189
29190             // Force a reflow on gecko based browsers to prevent jump/flicker.
29191             // This seems to happen on only certain configurations; it was originally
29192             // noticed in FF 2.0 and Linux.
29193             if (OpenLayers.IS_GECKO === true) {
29194                 this.div.scrollLeft = this.div.scrollLeft;
29195             }
29196             
29197             if (!zoomChanged && coordSysUnchanged) {
29198                 for (var i in this.unrenderedFeatures) {
29199                     var feature = this.unrenderedFeatures[i];
29200                     this.drawFeature(feature);
29201                 }
29202             }
29203         }
29204         if (!this.drawn || zoomChanged || !coordSysUnchanged) {
29205             this.drawn = true;
29206             var feature;
29207             for(var i=0, len=this.features.length; i<len; i++) {
29208                 this.renderer.locked = (i !== (len - 1));
29209                 feature = this.features[i];
29210                 this.drawFeature(feature);
29211             }
29212         }    
29213     },
29214     
29215     /** 
29216      * APIMethod: display
29217      * Hide or show the Layer
29218      * 
29219      * Parameters:
29220      * display - {Boolean}
29221      */
29222     display: function(display) {
29223         OpenLayers.Layer.prototype.display.apply(this, arguments);
29224         // we need to set the display style of the root in case it is attached
29225         // to a foreign layer
29226         var currentDisplay = this.div.style.display;
29227         if(currentDisplay != this.renderer.root.style.display) {
29228             this.renderer.root.style.display = currentDisplay;
29229         }
29230     },
29231
29232     /**
29233      * APIMethod: addFeatures
29234      * Add Features to the layer.
29235      *
29236      * Parameters:
29237      * features - {Array(<OpenLayers.Feature.Vector>)} 
29238      * options - {Object}
29239      */
29240     addFeatures: function(features, options) {
29241         if (!(OpenLayers.Util.isArray(features))) {
29242             features = [features];
29243         }
29244         
29245         var notify = !options || !options.silent;
29246         if(notify) {
29247             var event = {features: features};
29248             var ret = this.events.triggerEvent("beforefeaturesadded", event);
29249             if(ret === false) {
29250                 return;
29251             }
29252             features = event.features;
29253         }
29254         
29255         // Track successfully added features for featuresadded event, since
29256         // beforefeatureadded can veto single features.
29257         var featuresAdded = [];
29258         for (var i=0, len=features.length; i<len; i++) {
29259             if (i != (features.length - 1)) {
29260                 this.renderer.locked = true;
29261             } else {
29262                 this.renderer.locked = false;
29263             }    
29264             var feature = features[i];
29265             
29266             if (this.geometryType &&
29267               !(feature.geometry instanceof this.geometryType)) {
29268                 throw new TypeError('addFeatures: component should be an ' +
29269                                     this.geometryType.prototype.CLASS_NAME);
29270               }
29271
29272             //give feature reference to its layer
29273             feature.layer = this;
29274
29275             if (!feature.style && this.style) {
29276                 feature.style = OpenLayers.Util.extend({}, this.style);
29277             }
29278
29279             if (notify) {
29280                 if(this.events.triggerEvent("beforefeatureadded",
29281                                             {feature: feature}) === false) {
29282                     continue;
29283                 }
29284                 this.preFeatureInsert(feature);
29285             }
29286
29287             featuresAdded.push(feature);
29288             this.features.push(feature);
29289             this.drawFeature(feature);
29290             
29291             if (notify) {
29292                 this.events.triggerEvent("featureadded", {
29293                     feature: feature
29294                 });
29295                 this.onFeatureInsert(feature);
29296             }
29297         }
29298         
29299         if(notify) {
29300             this.events.triggerEvent("featuresadded", {features: featuresAdded});
29301         }
29302     },
29303
29304
29305     /**
29306      * APIMethod: removeFeatures
29307      * Remove features from the layer.  This erases any drawn features and
29308      *     removes them from the layer's control.  The beforefeatureremoved
29309      *     and featureremoved events will be triggered for each feature.  The
29310      *     featuresremoved event will be triggered after all features have
29311      *     been removed.  To suppress event triggering, use the silent option.
29312      * 
29313      * Parameters:
29314      * features - {Array(<OpenLayers.Feature.Vector>)} List of features to be
29315      *     removed.
29316      * options - {Object} Optional properties for changing behavior of the
29317      *     removal.
29318      *
29319      * Valid options:
29320      * silent - {Boolean} Suppress event triggering.  Default is false.
29321      */
29322     removeFeatures: function(features, options) {
29323         if(!features || features.length === 0) {
29324             return;
29325         }
29326         if (features === this.features) {
29327             return this.removeAllFeatures(options);
29328         }
29329         if (!(OpenLayers.Util.isArray(features))) {
29330             features = [features];
29331         }
29332         if (features === this.selectedFeatures) {
29333             features = features.slice();
29334         }
29335
29336         var notify = !options || !options.silent;
29337         
29338         if (notify) {
29339             this.events.triggerEvent(
29340                 "beforefeaturesremoved", {features: features}
29341             );
29342         }
29343
29344         for (var i = features.length - 1; i >= 0; i--) {
29345             // We remain locked so long as we're not at 0
29346             // and the 'next' feature has a geometry. We do the geometry check
29347             // because if all the features after the current one are 'null', we
29348             // won't call eraseGeometry, so we break the 'renderer functions
29349             // will always be called with locked=false *last*' rule. The end result
29350             // is a possible gratiutious unlocking to save a loop through the rest 
29351             // of the list checking the remaining features every time. So long as
29352             // null geoms are rare, this is probably okay.    
29353             if (i != 0 && features[i-1].geometry) {
29354                 this.renderer.locked = true;
29355             } else {
29356                 this.renderer.locked = false;
29357             }
29358     
29359             var feature = features[i];
29360             delete this.unrenderedFeatures[feature.id];
29361
29362             if (notify) {
29363                 this.events.triggerEvent("beforefeatureremoved", {
29364                     feature: feature
29365                 });
29366             }
29367
29368             this.features = OpenLayers.Util.removeItem(this.features, feature);
29369             // feature has no layer at this point
29370             feature.layer = null;
29371
29372             if (feature.geometry) {
29373                 this.renderer.eraseFeatures(feature);
29374             }
29375                     
29376             //in the case that this feature is one of the selected features, 
29377             // remove it from that array as well.
29378             if (OpenLayers.Util.indexOf(this.selectedFeatures, feature) != -1){
29379                 OpenLayers.Util.removeItem(this.selectedFeatures, feature);
29380             }
29381
29382             if (notify) {
29383                 this.events.triggerEvent("featureremoved", {
29384                     feature: feature
29385                 });
29386             }
29387         }
29388
29389         if (notify) {
29390             this.events.triggerEvent("featuresremoved", {features: features});
29391         }
29392     },
29393     
29394     /** 
29395      * APIMethod: removeAllFeatures
29396      * Remove all features from the layer.
29397      *
29398      * Parameters:
29399      * options - {Object} Optional properties for changing behavior of the
29400      *     removal.
29401      *
29402      * Valid options:
29403      * silent - {Boolean} Suppress event triggering.  Default is false.
29404      */
29405     removeAllFeatures: function(options) {
29406         var notify = !options || !options.silent;
29407         var features = this.features;
29408         if (notify) {
29409             this.events.triggerEvent(
29410                 "beforefeaturesremoved", {features: features}
29411             );
29412         }
29413         var feature;
29414         for (var i = features.length-1; i >= 0; i--) {
29415             feature = features[i];
29416             if (notify) {
29417                 this.events.triggerEvent("beforefeatureremoved", {
29418                     feature: feature
29419                 });
29420             }
29421             feature.layer = null;
29422             if (notify) {
29423                 this.events.triggerEvent("featureremoved", {
29424                     feature: feature
29425                 });
29426             }
29427         }
29428         this.renderer.clear();
29429         this.features = [];
29430         this.unrenderedFeatures = {};
29431         this.selectedFeatures = [];
29432         if (notify) {
29433             this.events.triggerEvent("featuresremoved", {features: features});
29434         }
29435     },
29436
29437     /**
29438      * APIMethod: destroyFeatures
29439      * Erase and destroy features on the layer.
29440      *
29441      * Parameters:
29442      * features - {Array(<OpenLayers.Feature.Vector>)} An optional array of
29443      *     features to destroy.  If not supplied, all features on the layer
29444      *     will be destroyed.
29445      * options - {Object}
29446      */
29447     destroyFeatures: function(features, options) {
29448         var all = (features == undefined); // evaluates to true if
29449                                            // features is null
29450         if(all) {
29451             features = this.features;
29452         }
29453         if(features) {
29454             this.removeFeatures(features, options);
29455             for(var i=features.length-1; i>=0; i--) {
29456                 features[i].destroy();
29457             }
29458         }
29459     },
29460
29461     /**
29462      * APIMethod: drawFeature
29463      * Draw (or redraw) a feature on the layer.  If the optional style argument
29464      * is included, this style will be used.  If no style is included, the
29465      * feature's style will be used.  If the feature doesn't have a style,
29466      * the layer's style will be used.
29467      * 
29468      * This function is not designed to be used when adding features to 
29469      * the layer (use addFeatures instead). It is meant to be used when
29470      * the style of a feature has changed, or in some other way needs to 
29471      * visually updated *after* it has already been added to a layer. You
29472      * must add the feature to the layer for most layer-related events to 
29473      * happen.
29474      *
29475      * Parameters: 
29476      * feature - {<OpenLayers.Feature.Vector>} 
29477      * style - {String | Object} Named render intent or full symbolizer object.
29478      */
29479     drawFeature: function(feature, style) {
29480         // don't try to draw the feature with the renderer if the layer is not 
29481         // drawn itself
29482         if (!this.drawn) {
29483             return;
29484         }
29485         if (typeof style != "object") {
29486             if(!style && feature.state === OpenLayers.State.DELETE) {
29487                 style = "delete";
29488             }
29489             var renderIntent = style || feature.renderIntent;
29490             style = feature.style || this.style;
29491             if (!style) {
29492                 style = this.styleMap.createSymbolizer(feature, renderIntent);
29493             }
29494         }
29495         
29496         var drawn = this.renderer.drawFeature(feature, style);
29497         //TODO remove the check for null when we get rid of Renderer.SVG
29498         if (drawn === false || drawn === null) {
29499             this.unrenderedFeatures[feature.id] = feature;
29500         } else {
29501             delete this.unrenderedFeatures[feature.id];
29502         }
29503     },
29504     
29505     /**
29506      * Method: eraseFeatures
29507      * Erase features from the layer.
29508      *
29509      * Parameters:
29510      * features - {Array(<OpenLayers.Feature.Vector>)} 
29511      */
29512     eraseFeatures: function(features) {
29513         this.renderer.eraseFeatures(features);
29514     },
29515
29516     /**
29517      * Method: getFeatureFromEvent
29518      * Given an event, return a feature if the event occurred over one.
29519      * Otherwise, return null.
29520      *
29521      * Parameters:
29522      * evt - {Event} 
29523      *
29524      * Returns:
29525      * {<OpenLayers.Feature.Vector>} A feature if one was under the event.
29526      */
29527     getFeatureFromEvent: function(evt) {
29528         if (!this.renderer) {
29529             throw new Error('getFeatureFromEvent called on layer with no ' +
29530                             'renderer. This usually means you destroyed a ' +
29531                             'layer, but not some handler which is associated ' +
29532                             'with it.');
29533         }
29534         var feature = null;
29535         var featureId = this.renderer.getFeatureIdFromEvent(evt);
29536         if (featureId) {
29537             if (typeof featureId === "string") {
29538                 feature = this.getFeatureById(featureId);
29539             } else {
29540                 feature = featureId;
29541             }
29542         }
29543         return feature;
29544     },
29545
29546     /**
29547      * APIMethod: getFeatureBy
29548      * Given a property value, return the feature if it exists in the features array
29549      *
29550      * Parameters:
29551      * property - {String}
29552      * value - {String}
29553      *
29554      * Returns:
29555      * {<OpenLayers.Feature.Vector>} A feature corresponding to the given
29556      * property value or null if there is no such feature.
29557      */
29558     getFeatureBy: function(property, value) {
29559         //TBD - would it be more efficient to use a hash for this.features?
29560         var feature = null;
29561         for(var i=0, len=this.features.length; i<len; ++i) {
29562             if(this.features[i][property] == value) {
29563                 feature = this.features[i];
29564                 break;
29565             }
29566         }
29567         return feature;
29568     },
29569
29570     /**
29571      * APIMethod: getFeatureById
29572      * Given a feature id, return the feature if it exists in the features array
29573      *
29574      * Parameters:
29575      * featureId - {String}
29576      *
29577      * Returns:
29578      * {<OpenLayers.Feature.Vector>} A feature corresponding to the given
29579      * featureId or null if there is no such feature.
29580      */
29581     getFeatureById: function(featureId) {
29582         return this.getFeatureBy('id', featureId);
29583     },
29584
29585     /**
29586      * APIMethod: getFeatureByFid
29587      * Given a feature fid, return the feature if it exists in the features array
29588      *
29589      * Parameters:
29590      * featureFid - {String}
29591      *
29592      * Returns:
29593      * {<OpenLayers.Feature.Vector>} A feature corresponding to the given
29594      * featureFid or null if there is no such feature.
29595      */
29596     getFeatureByFid: function(featureFid) {
29597         return this.getFeatureBy('fid', featureFid);
29598     },
29599     
29600     /**
29601      * APIMethod: getFeaturesByAttribute
29602      * Returns an array of features that have the given attribute key set to the
29603      * given value. Comparison of attribute values takes care of datatypes, e.g.
29604      * the string '1234' is not equal to the number 1234.
29605      *
29606      * Parameters:
29607      * attrName - {String}
29608      * attrValue - {Mixed}
29609      *
29610      * Returns:
29611      * Array({<OpenLayers.Feature.Vector>}) An array of features that have the 
29612      * passed named attribute set to the given value.
29613      */
29614     getFeaturesByAttribute: function(attrName, attrValue) {
29615         var i,
29616             feature,    
29617             len = this.features.length,
29618             foundFeatures = [];
29619         for(i = 0; i < len; i++) {            
29620             feature = this.features[i];
29621             if(feature && feature.attributes) {
29622                 if (feature.attributes[attrName] === attrValue) {
29623                     foundFeatures.push(feature);
29624                 }
29625             }
29626         }
29627         return foundFeatures;
29628     },
29629
29630     /**
29631      * Unselect the selected features
29632      * i.e. clears the featureSelection array
29633      * change the style back
29634     clearSelection: function() {
29635
29636        var vectorLayer = this.map.vectorLayer;
29637         for (var i = 0; i < this.map.featureSelection.length; i++) {
29638             var featureSelection = this.map.featureSelection[i];
29639             vectorLayer.drawFeature(featureSelection, vectorLayer.style);
29640         }
29641         this.map.featureSelection = [];
29642     },
29643      */
29644
29645
29646     /**
29647      * APIMethod: onFeatureInsert
29648      * method called after a feature is inserted.
29649      * Does nothing by default. Override this if you
29650      * need to do something on feature updates.
29651      *
29652      * Parameters: 
29653      * feature - {<OpenLayers.Feature.Vector>} 
29654      */
29655     onFeatureInsert: function(feature) {
29656     },
29657     
29658     /**
29659      * APIMethod: preFeatureInsert
29660      * method called before a feature is inserted.
29661      * Does nothing by default. Override this if you
29662      * need to do something when features are first added to the
29663      * layer, but before they are drawn, such as adjust the style.
29664      *
29665      * Parameters:
29666      * feature - {<OpenLayers.Feature.Vector>} 
29667      */
29668     preFeatureInsert: function(feature) {
29669     },
29670
29671     /** 
29672      * APIMethod: getDataExtent
29673      * Calculates the max extent which includes all of the features.
29674      * 
29675      * Returns:
29676      * {<OpenLayers.Bounds>} or null if the layer has no features with
29677      * geometries.
29678      */
29679     getDataExtent: function () {
29680         var maxExtent = null;
29681         var features = this.features;
29682         if(features && (features.length > 0)) {
29683             var geometry = null;
29684             for(var i=0, len=features.length; i<len; i++) {
29685                 geometry = features[i].geometry;
29686                 if (geometry) {
29687                     if (maxExtent === null) {
29688                         maxExtent = new OpenLayers.Bounds();
29689                     }
29690                     maxExtent.extend(geometry.getBounds());
29691                 }
29692             }
29693         }
29694         return maxExtent;
29695     },
29696
29697     CLASS_NAME: "OpenLayers.Layer.Vector"
29698 });
29699 /* ======================================================================
29700     OpenLayers/Layer/Vector/RootContainer.js
29701    ====================================================================== */
29702
29703 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
29704  * full list of contributors). Published under the 2-clause BSD license.
29705  * See license.txt in the OpenLayers distribution or repository for the
29706  * full text of the license. */
29707
29708 /**
29709  * @requires OpenLayers/Layer/Vector.js
29710  */
29711
29712 /**
29713  * Class: OpenLayers.Layer.Vector.RootContainer
29714  * A special layer type to combine multiple vector layers inside a single
29715  *     renderer root container. This class is not supposed to be instantiated
29716  *     from user space, it is a helper class for controls that require event
29717  *     processing for multiple vector layers.
29718  *
29719  * Inherits from:
29720  *  - <OpenLayers.Layer.Vector>
29721  */
29722 OpenLayers.Layer.Vector.RootContainer = OpenLayers.Class(OpenLayers.Layer.Vector, {
29723     
29724     /**
29725      * Property: displayInLayerSwitcher
29726      * Set to false for this layer type
29727      */
29728     displayInLayerSwitcher: false,
29729     
29730     /**
29731      * APIProperty: layers
29732      * Layers that are attached to this container. Required config option.
29733      */
29734     layers: null,
29735     
29736     /**
29737      * Constructor: OpenLayers.Layer.Vector.RootContainer
29738      * Create a new root container for multiple vector layer. This constructor
29739      * is not supposed to be used from user space, it is only to be used by
29740      * controls that need feature selection across multiple vector layers.
29741      *
29742      * Parameters:
29743      * name - {String} A name for the layer
29744      * options - {Object} Optional object with non-default properties to set on
29745      *           the layer.
29746      * 
29747      * Required options properties:
29748      * layers - {Array(<OpenLayers.Layer.Vector>)} The layers managed by this
29749      *     container
29750      *
29751      * Returns:
29752      * {<OpenLayers.Layer.Vector.RootContainer>} A new vector layer root
29753      *     container
29754      */
29755     
29756     /**
29757      * Method: display
29758      */
29759     display: function() {},
29760     
29761     /**
29762      * Method: getFeatureFromEvent
29763      * walk through the layers to find the feature returned by the event
29764      * 
29765      * Parameters:
29766      * evt - {Object} event object with a feature property
29767      * 
29768      * Returns:
29769      * {<OpenLayers.Feature.Vector>}
29770      */
29771     getFeatureFromEvent: function(evt) {
29772         var layers = this.layers;
29773         var feature;
29774         for(var i=0; i<layers.length; i++) {
29775             feature = layers[i].getFeatureFromEvent(evt);
29776             if(feature) {
29777                 return feature;
29778             }
29779         }
29780     },
29781     
29782     /**
29783      * Method: setMap
29784      * 
29785      * Parameters:
29786      * map - {<OpenLayers.Map>}
29787      */
29788     setMap: function(map) {
29789         OpenLayers.Layer.Vector.prototype.setMap.apply(this, arguments);
29790         this.collectRoots();
29791         map.events.register("changelayer", this, this.handleChangeLayer);
29792     },
29793     
29794     /**
29795      * Method: removeMap
29796      * 
29797      * Parameters:
29798      * map - {<OpenLayers.Map>}
29799      */
29800     removeMap: function(map) {
29801         map.events.unregister("changelayer", this, this.handleChangeLayer);
29802         this.resetRoots();
29803         OpenLayers.Layer.Vector.prototype.removeMap.apply(this, arguments);
29804     },
29805     
29806     /**
29807      * Method: collectRoots
29808      * Collects the root nodes of all layers this control is configured with
29809      * and moveswien the nodes to this control's layer
29810      */
29811     collectRoots: function() {
29812         var layer;
29813         // walk through all map layers, because we want to keep the order
29814         for(var i=0; i<this.map.layers.length; ++i) {
29815             layer = this.map.layers[i];
29816             if(OpenLayers.Util.indexOf(this.layers, layer) != -1) {
29817                 layer.renderer.moveRoot(this.renderer);
29818             }
29819         }
29820     },
29821     
29822     /**
29823      * Method: resetRoots
29824      * Resets the root nodes back into the layers they belong to.
29825      */
29826     resetRoots: function() {
29827         var layer;
29828         for(var i=0; i<this.layers.length; ++i) {
29829             layer = this.layers[i];
29830             if(this.renderer && layer.renderer.getRenderLayerId() == this.id) {
29831                 this.renderer.moveRoot(layer.renderer);
29832             }
29833         }
29834     },
29835     
29836     /**
29837      * Method: handleChangeLayer
29838      * Event handler for the map's changelayer event. We need to rebuild
29839      * this container's layer dom if order of one of its layers changes.
29840      * This handler is added with the setMap method, and removed with the
29841      * removeMap method.
29842      * 
29843      * Parameters:
29844      * evt - {Object}
29845      */
29846     handleChangeLayer: function(evt) {
29847         var layer = evt.layer;
29848         if(evt.property == "order" &&
29849                         OpenLayers.Util.indexOf(this.layers, layer) != -1) {
29850             this.resetRoots();
29851             this.collectRoots();
29852         }
29853     },
29854
29855     CLASS_NAME: "OpenLayers.Layer.Vector.RootContainer"
29856 });
29857 /* ======================================================================
29858     OpenLayers/Control/SelectFeature.js
29859    ====================================================================== */
29860
29861 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
29862  * full list of contributors). Published under the 2-clause BSD license.
29863  * See license.txt in the OpenLayers distribution or repository for the
29864  * full text of the license. */
29865
29866
29867 /**
29868  * @requires OpenLayers/Control.js
29869  * @requires OpenLayers/Feature/Vector.js
29870  * @requires OpenLayers/Handler/Feature.js
29871  * @requires OpenLayers/Layer/Vector/RootContainer.js
29872  */
29873
29874 /**
29875  * Class: OpenLayers.Control.SelectFeature
29876  * The SelectFeature control selects vector features from a given layer on
29877  * click or hover.
29878  *
29879  * Inherits from:
29880  *  - <OpenLayers.Control>
29881  */
29882 OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, {
29883
29884     /**
29885      * APIProperty: events
29886      * {<OpenLayers.Events>} Events instance for listeners and triggering
29887      *     control specific events.
29888      *
29889      * Register a listener for a particular event with the following syntax:
29890      * (code)
29891      * control.events.register(type, obj, listener);
29892      * (end)
29893      *
29894      * Supported event types (in addition to those from <OpenLayers.Control.events>):
29895      * beforefeaturehighlighted - Triggered before a feature is highlighted
29896      * featurehighlighted - Triggered when a feature is highlighted
29897      * featureunhighlighted - Triggered when a feature is unhighlighted
29898      * boxselectionstart - Triggered before box selection starts
29899      * boxselectionend - Triggered after box selection ends
29900      */
29901
29902     /**
29903      * APIProperty: multipleKey
29904      * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
29905      *     the <multiple> property to true.  Default is null.
29906      */
29907     multipleKey: null,
29908
29909     /**
29910      * APIProperty: toggleKey
29911      * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
29912      *     the <toggle> property to true.  Default is null.
29913      */
29914     toggleKey: null,
29915
29916     /**
29917      * APIProperty: multiple
29918      * {Boolean} Allow selection of multiple geometries.  Default is false.
29919      */
29920     multiple: false,
29921
29922     /**
29923      * APIProperty: clickout
29924      * {Boolean} Unselect features when clicking outside any feature.
29925      *     Default is true.
29926      */
29927     clickout: true,
29928
29929     /**
29930      * APIProperty: toggle
29931      * {Boolean} Unselect a selected feature on click.  Default is false.  Only
29932      *     has meaning if hover is false.
29933      */
29934     toggle: false,
29935
29936     /**
29937      * APIProperty: hover
29938      * {Boolean} Select on mouse over and deselect on mouse out.  If true, this
29939      * ignores clicks and only listens to mouse moves.
29940      */
29941     hover: false,
29942
29943     /**
29944      * APIProperty: highlightOnly
29945      * {Boolean} If true do not actually select features (that is place them in
29946      * the layer's selected features array), just highlight them. This property
29947      * has no effect if hover is false. Defaults to false.
29948      */
29949     highlightOnly: false,
29950
29951     /**
29952      * APIProperty: box
29953      * {Boolean} Allow feature selection by drawing a box.
29954      */
29955     box: false,
29956
29957     /**
29958      * APIProperty: onBeforeSelect
29959      * {Function} Optional function to be called before a feature is selected.
29960      *     The function should expect to be called with a feature.
29961      */
29962     onBeforeSelect: function() {},
29963
29964     /**
29965      * APIProperty: onSelect
29966      * {Function} Optional function to be called when a feature is selected.
29967      *     The function should expect to be called with a feature.
29968      */
29969     onSelect: function() {},
29970
29971     /**
29972      * APIProperty: onUnselect
29973      * {Function} Optional function to be called when a feature is unselected.
29974      *     The function should expect to be called with a feature.
29975      */
29976     onUnselect: function() {},
29977
29978     /**
29979      * APIProperty: scope
29980      * {Object} The scope to use with the onBeforeSelect, onSelect, onUnselect
29981      *     callbacks. If null the scope will be this control.
29982      */
29983     scope: null,
29984
29985     /**
29986      * APIProperty: geometryTypes
29987      * {Array(String)} To restrict selecting to a limited set of geometry types,
29988      *     send a list of strings corresponding to the geometry class names.
29989      */
29990     geometryTypes: null,
29991
29992     /**
29993      * Property: layer
29994      * {<OpenLayers.Layer.Vector>} The vector layer with a common renderer
29995      * root for all layers this control is configured with (if an array of
29996      * layers was passed to the constructor), or the vector layer the control
29997      * was configured with (if a single layer was passed to the constructor).
29998      */
29999     layer: null,
30000
30001     /**
30002      * Property: layers
30003      * {Array(<OpenLayers.Layer.Vector>)} The layers this control will work on,
30004      * or null if the control was configured with a single layer
30005      */
30006     layers: null,
30007
30008     /**
30009      * APIProperty: callbacks
30010      * {Object} The functions that are sent to the handlers.feature for callback
30011      */
30012     callbacks: null,
30013
30014     /**
30015      * APIProperty: selectStyle
30016      * {Object} Hash of styles
30017      */
30018     selectStyle: null,
30019
30020     /**
30021      * APIProperty: renderIntent
30022      * {String} key used to retrieve the select style from the layer's
30023      * style map.
30024      */
30025     renderIntent: "select",
30026
30027     /**
30028      * Property: handlers
30029      * {Object} Object with references to multiple <OpenLayers.Handler>
30030      *     instances.
30031      */
30032     handlers: null,
30033
30034     /**
30035      * Constructor: OpenLayers.Control.SelectFeature
30036      * Create a new control for selecting features.
30037      *
30038      * Parameters:
30039      * layers - {<OpenLayers.Layer.Vector>}, or an array of vector layers. The
30040      *     layer(s) this control will select features from.
30041      * options - {Object}
30042      */
30043     initialize: function(layers, options) {
30044         OpenLayers.Control.prototype.initialize.apply(this, [options]);
30045
30046         if(this.scope === null) {
30047             this.scope = this;
30048         }
30049         this.initLayer(layers);
30050         var callbacks = {
30051             click: this.clickFeature,
30052             clickout: this.clickoutFeature
30053         };
30054         if (this.hover) {
30055             callbacks.over = this.overFeature;
30056             callbacks.out = this.outFeature;
30057         }
30058
30059         this.callbacks = OpenLayers.Util.extend(callbacks, this.callbacks);
30060         this.handlers = {
30061             feature: new OpenLayers.Handler.Feature(
30062                 this, this.layer, this.callbacks,
30063                 {geometryTypes: this.geometryTypes}
30064             )
30065         };
30066
30067         if (this.box) {
30068             this.handlers.box = new OpenLayers.Handler.Box(
30069                 this, {done: this.selectBox},
30070                 {boxDivClassName: "olHandlerBoxSelectFeature"}
30071             );
30072         }
30073     },
30074
30075     /**
30076      * Method: initLayer
30077      * Assign the layer property. If layers is an array, we need to use
30078      *     a RootContainer.
30079      *
30080      * Parameters:
30081      * layers - {<OpenLayers.Layer.Vector>}, or an array of vector layers.
30082      */
30083     initLayer: function(layers) {
30084         if(OpenLayers.Util.isArray(layers)) {
30085             this.layers = layers;
30086             this.layer = new OpenLayers.Layer.Vector.RootContainer(
30087                 this.id + "_container", {
30088                     layers: layers
30089                 }
30090             );
30091         } else {
30092             this.layer = layers;
30093         }
30094     },
30095
30096     /**
30097      * Method: destroy
30098      */
30099     destroy: function() {
30100         if(this.active && this.layers) {
30101             this.map.removeLayer(this.layer);
30102         }
30103         OpenLayers.Control.prototype.destroy.apply(this, arguments);
30104         if(this.layers) {
30105             this.layer.destroy();
30106         }
30107     },
30108
30109     /**
30110      * Method: activate
30111      * Activates the control.
30112      *
30113      * Returns:
30114      * {Boolean} The control was effectively activated.
30115      */
30116     activate: function () {
30117         if (!this.active) {
30118             if(this.layers) {
30119                 this.map.addLayer(this.layer);
30120             }
30121             this.handlers.feature.activate();
30122             if(this.box && this.handlers.box) {
30123                 this.handlers.box.activate();
30124             }
30125         }
30126         return OpenLayers.Control.prototype.activate.apply(
30127             this, arguments
30128         );
30129     },
30130
30131     /**
30132      * Method: deactivate
30133      * Deactivates the control.
30134      *
30135      * Returns:
30136      * {Boolean} The control was effectively deactivated.
30137      */
30138     deactivate: function () {
30139         if (this.active) {
30140             this.handlers.feature.deactivate();
30141             if(this.handlers.box) {
30142                 this.handlers.box.deactivate();
30143             }
30144             if(this.layers) {
30145                 this.map.removeLayer(this.layer);
30146             }
30147         }
30148         return OpenLayers.Control.prototype.deactivate.apply(
30149             this, arguments
30150         );
30151     },
30152
30153     /**
30154      * Method: unselectAll
30155      * Unselect all selected features.  To unselect all except for a single
30156      *     feature, set the options.except property to the feature.
30157      *
30158      * Parameters:
30159      * options - {Object} Optional configuration object.
30160      */
30161     unselectAll: function(options) {
30162         // we'll want an option to suppress notification here
30163         var layers = this.layers || [this.layer],
30164             layer, feature, l, numExcept;
30165         for(l=0; l<layers.length; ++l) {
30166             layer = layers[l];
30167             numExcept = 0;
30168             //layer.selectedFeatures is null when layer is destroyed and
30169             //one of it's preremovelayer listener calls setLayer
30170             //with another layer on this control
30171             if(layer.selectedFeatures != null) {
30172                 while(layer.selectedFeatures.length > numExcept) {
30173                     feature = layer.selectedFeatures[numExcept];
30174                     if(!options || options.except != feature) {
30175                         this.unselect(feature);
30176                     } else {
30177                         ++numExcept;
30178                     }
30179                 }
30180             }
30181         }
30182     },
30183
30184     /**
30185      * Method: clickFeature
30186      * Called on click in a feature
30187      * Only responds if this.hover is false.
30188      *
30189      * Parameters:
30190      * feature - {<OpenLayers.Feature.Vector>}
30191      */
30192     clickFeature: function(feature) {
30193         if(!this.hover) {
30194             var selected = (OpenLayers.Util.indexOf(
30195                 feature.layer.selectedFeatures, feature) > -1);
30196             if(selected) {
30197                 if(this.toggleSelect()) {
30198                     this.unselect(feature);
30199                 } else if(!this.multipleSelect()) {
30200                     this.unselectAll({except: feature});
30201                 }
30202             } else {
30203                 if(!this.multipleSelect()) {
30204                     this.unselectAll({except: feature});
30205                 }
30206                 this.select(feature);
30207             }
30208         }
30209     },
30210
30211     /**
30212      * Method: multipleSelect
30213      * Allow for multiple selected features based on <multiple> property and
30214      *     <multipleKey> event modifier.
30215      *
30216      * Returns:
30217      * {Boolean} Allow for multiple selected features.
30218      */
30219     multipleSelect: function() {
30220         return this.multiple || (this.handlers.feature.evt &&
30221                                  this.handlers.feature.evt[this.multipleKey]);
30222     },
30223
30224     /**
30225      * Method: toggleSelect
30226      * Event should toggle the selected state of a feature based on <toggle>
30227      *     property and <toggleKey> event modifier.
30228      *
30229      * Returns:
30230      * {Boolean} Toggle the selected state of a feature.
30231      */
30232     toggleSelect: function() {
30233         return this.toggle || (this.handlers.feature.evt &&
30234                                this.handlers.feature.evt[this.toggleKey]);
30235     },
30236
30237     /**
30238      * Method: clickoutFeature
30239      * Called on click outside a previously clicked (selected) feature.
30240      * Only responds if this.hover is false.
30241      *
30242      * Parameters:
30243      * feature - {<OpenLayers.Vector.Feature>}
30244      */
30245     clickoutFeature: function(feature) {
30246         if(!this.hover && this.clickout) {
30247             this.unselectAll();
30248         }
30249     },
30250
30251     /**
30252      * Method: overFeature
30253      * Called on over a feature.
30254      * Only responds if this.hover is true.
30255      *
30256      * Parameters:
30257      * feature - {<OpenLayers.Feature.Vector>}
30258      */
30259     overFeature: function(feature) {
30260         var layer = feature.layer;
30261         if(this.hover) {
30262             if(this.highlightOnly) {
30263                 this.highlight(feature);
30264             } else if(OpenLayers.Util.indexOf(
30265                 layer.selectedFeatures, feature) == -1) {
30266                 this.select(feature);
30267             }
30268         }
30269     },
30270
30271     /**
30272      * Method: outFeature
30273      * Called on out of a selected feature.
30274      * Only responds if this.hover is true.
30275      *
30276      * Parameters:
30277      * feature - {<OpenLayers.Feature.Vector>}
30278      */
30279     outFeature: function(feature) {
30280         if(this.hover) {
30281             if(this.highlightOnly) {
30282                 // we do nothing if we're not the last highlighter of the
30283                 // feature
30284                 if(feature._lastHighlighter == this.id) {
30285                     // if another select control had highlighted the feature before
30286                     // we did it ourself then we use that control to highlight the
30287                     // feature as it was before we highlighted it, else we just
30288                     // unhighlight it
30289                     if(feature._prevHighlighter &&
30290                        feature._prevHighlighter != this.id) {
30291                         delete feature._lastHighlighter;
30292                         var control = this.map.getControl(
30293                             feature._prevHighlighter);
30294                         if(control) {
30295                             control.highlight(feature);
30296                         }
30297                     } else {
30298                         this.unhighlight(feature);
30299                     }
30300                 }
30301             } else {
30302                 this.unselect(feature);
30303             }
30304         }
30305     },
30306
30307     /**
30308      * Method: highlight
30309      * Redraw feature with the select style.
30310      *
30311      * Parameters:
30312      * feature - {<OpenLayers.Feature.Vector>}
30313      */
30314     highlight: function(feature) {
30315         var layer = feature.layer;
30316         var cont = this.events.triggerEvent("beforefeaturehighlighted", {
30317             feature : feature
30318         });
30319         if(cont !== false) {
30320             feature._prevHighlighter = feature._lastHighlighter;
30321             feature._lastHighlighter = this.id;
30322             var style = this.selectStyle || this.renderIntent;
30323             layer.drawFeature(feature, style);
30324             this.events.triggerEvent("featurehighlighted", {feature : feature});
30325         }
30326     },
30327
30328     /**
30329      * Method: unhighlight
30330      * Redraw feature with the "default" style
30331      *
30332      * Parameters:
30333      * feature - {<OpenLayers.Feature.Vector>}
30334      */
30335     unhighlight: function(feature) {
30336         var layer = feature.layer;
30337         // three cases:
30338         // 1. there's no other highlighter, in that case _prev is undefined,
30339         //    and we just need to undef _last
30340         // 2. another control highlighted the feature after we did it, in
30341         //    that case _last references this other control, and we just
30342         //    need to undef _prev
30343         // 3. another control highlighted the feature before we did it, in
30344         //    that case _prev references this other control, and we need to
30345         //    set _last to _prev and undef _prev
30346         if(feature._prevHighlighter == undefined) {
30347             delete feature._lastHighlighter;
30348         } else if(feature._prevHighlighter == this.id) {
30349             delete feature._prevHighlighter;
30350         } else {
30351             feature._lastHighlighter = feature._prevHighlighter;
30352             delete feature._prevHighlighter;
30353         }
30354         layer.drawFeature(feature, feature.style || feature.layer.style ||
30355             "default");
30356         this.events.triggerEvent("featureunhighlighted", {feature : feature});
30357     },
30358
30359     /**
30360      * Method: select
30361      * Add feature to the layer's selectedFeature array, render the feature as
30362      * selected, and call the onSelect function.
30363      *
30364      * Parameters:
30365      * feature - {<OpenLayers.Feature.Vector>}
30366      */
30367     select: function(feature) {
30368         var cont = this.onBeforeSelect.call(this.scope, feature);
30369         var layer = feature.layer;
30370         if(cont !== false) {
30371             cont = layer.events.triggerEvent("beforefeatureselected", {
30372                 feature: feature
30373             });
30374             if(cont !== false) {
30375                 layer.selectedFeatures.push(feature);
30376                 this.highlight(feature);
30377                 // if the feature handler isn't involved in the feature
30378                 // selection (because the box handler is used or the
30379                 // feature is selected programatically) we fake the
30380                 // feature handler to allow unselecting on click
30381                 if(!this.handlers.feature.lastFeature) {
30382                     this.handlers.feature.lastFeature = layer.selectedFeatures[0];
30383                 }
30384                 layer.events.triggerEvent("featureselected", {feature: feature});
30385                 this.onSelect.call(this.scope, feature);
30386             }
30387         }
30388     },
30389
30390     /**
30391      * Method: unselect
30392      * Remove feature from the layer's selectedFeature array, render the feature as
30393      * normal, and call the onUnselect function.
30394      *
30395      * Parameters:
30396      * feature - {<OpenLayers.Feature.Vector>}
30397      */
30398     unselect: function(feature) {
30399         var layer = feature.layer;
30400         // Store feature style for restoration later
30401         this.unhighlight(feature);
30402         OpenLayers.Util.removeItem(layer.selectedFeatures, feature);
30403         layer.events.triggerEvent("featureunselected", {feature: feature});
30404         this.onUnselect.call(this.scope, feature);
30405     },
30406
30407     /**
30408      * Method: selectBox
30409      * Callback from the handlers.box set up when <box> selection is true
30410      *     on.
30411      *
30412      * Parameters:
30413      * position - {<OpenLayers.Bounds> || <OpenLayers.Pixel> }
30414      */
30415     selectBox: function(position) {
30416         if (position instanceof OpenLayers.Bounds) {
30417             var minXY = this.map.getLonLatFromPixel({
30418                 x: position.left,
30419                 y: position.bottom
30420             });
30421             var maxXY = this.map.getLonLatFromPixel({
30422                 x: position.right,
30423                 y: position.top
30424             });
30425             var bounds = new OpenLayers.Bounds(
30426                 minXY.lon, minXY.lat, maxXY.lon, maxXY.lat
30427             );
30428
30429             // if multiple is false, first deselect currently selected features
30430             if (!this.multipleSelect()) {
30431                 this.unselectAll();
30432             }
30433
30434             // because we're using a box, we consider we want multiple selection
30435             var prevMultiple = this.multiple;
30436             this.multiple = true;
30437             var layers = this.layers || [this.layer];
30438             this.events.triggerEvent("boxselectionstart", {layers: layers});
30439             var layer;
30440             for(var l=0; l<layers.length; ++l) {
30441                 layer = layers[l];
30442                 for(var i=0, len = layer.features.length; i<len; ++i) {
30443                     var feature = layer.features[i];
30444                     // check if the feature is displayed
30445                     if (!feature.getVisibility()) {
30446                         continue;
30447                     }
30448
30449                     if (this.geometryTypes == null || OpenLayers.Util.indexOf(
30450                             this.geometryTypes, feature.geometry.CLASS_NAME) > -1) {
30451                         if (bounds.toGeometry().intersects(feature.geometry)) {
30452                             if (OpenLayers.Util.indexOf(layer.selectedFeatures, feature) == -1) {
30453                                 this.select(feature);
30454                             }
30455                         }
30456                     }
30457                 }
30458             }
30459             this.multiple = prevMultiple;
30460             this.events.triggerEvent("boxselectionend", {layers: layers});
30461         }
30462     },
30463
30464     /**
30465      * Method: setMap
30466      * Set the map property for the control.
30467      *
30468      * Parameters:
30469      * map - {<OpenLayers.Map>}
30470      */
30471     setMap: function(map) {
30472         this.handlers.feature.setMap(map);
30473         if (this.box) {
30474             this.handlers.box.setMap(map);
30475         }
30476         OpenLayers.Control.prototype.setMap.apply(this, arguments);
30477     },
30478
30479     /**
30480      * APIMethod: setLayer
30481      * Attach a new layer to the control, overriding any existing layers.
30482      *
30483      * Parameters:
30484      * layers - Array of {<OpenLayers.Layer.Vector>} or a single
30485      *     {<OpenLayers.Layer.Vector>}
30486      */
30487     setLayer: function(layers) {
30488         var isActive = this.active;
30489         this.unselectAll();
30490         this.deactivate();
30491         if(this.layers) {
30492             this.layer.destroy();
30493             this.layers = null;
30494         }
30495         this.initLayer(layers);
30496         this.handlers.feature.layer = this.layer;
30497         if (isActive) {
30498             this.activate();
30499         }
30500     },
30501     
30502     /**
30503      * APIMethod: addLayer
30504      * Add a layer to the control, making the existing layers still selectable
30505      *
30506      * Parameters:
30507      * layer - element <OpenLayers.Layer.Vector> 
30508      */
30509     addLayer: function( layer ) {
30510         var isActive = this.active;
30511         this.deactivate();
30512         if (this.layers == null) {
30513             if (this.layer != null) {
30514                 this.layers = [this.layer];
30515                 this.layers.push(layer);
30516             } else {
30517                 this.layers = [layer];
30518             }
30519         } else {        
30520             this.layers.push(layer);
30521         }
30522         this.initLayer(this.layers);
30523         this.handlers.feature.layer = this.layer;
30524         if (isActive) {
30525             this.activate();
30526         }
30527     },      
30528
30529     CLASS_NAME: "OpenLayers.Control.SelectFeature"
30530 });
30531 /* ======================================================================
30532     OpenLayers/Format/JSON.js
30533    ====================================================================== */
30534
30535 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
30536  * full list of contributors). Published under the 2-clause BSD license.
30537  * See license.txt in the OpenLayers distribution or repository for the
30538  * full text of the license. */
30539
30540 /**
30541  * Note:
30542  * This work draws heavily from the public domain JSON serializer/deserializer
30543  *     at http://www.json.org/json.js. Rewritten so that it doesn't modify
30544  *     basic data prototypes.
30545  */
30546
30547 /**
30548  * @requires OpenLayers/Format.js
30549  */
30550
30551 /**
30552  * Class: OpenLayers.Format.JSON
30553  * A parser to read/write JSON safely.  Create a new instance with the
30554  *     <OpenLayers.Format.JSON> constructor.
30555  *
30556  * Inherits from:
30557  *  - <OpenLayers.Format>
30558  */
30559 OpenLayers.Format.JSON = OpenLayers.Class(OpenLayers.Format, {
30560     
30561     /**
30562      * APIProperty: indent
30563      * {String} For "pretty" printing, the indent string will be used once for
30564      *     each indentation level.
30565      */
30566     indent: "    ",
30567     
30568     /**
30569      * APIProperty: space
30570      * {String} For "pretty" printing, the space string will be used after
30571      *     the ":" separating a name/value pair.
30572      */
30573     space: " ",
30574     
30575     /**
30576      * APIProperty: newline
30577      * {String} For "pretty" printing, the newline string will be used at the
30578      *     end of each name/value pair or array item.
30579      */
30580     newline: "\n",
30581     
30582     /**
30583      * Property: level
30584      * {Integer} For "pretty" printing, this is incremented/decremented during
30585      *     serialization.
30586      */
30587     level: 0,
30588
30589     /**
30590      * Property: pretty
30591      * {Boolean} Serialize with extra whitespace for structure.  This is set
30592      *     by the <write> method.
30593      */
30594     pretty: false,
30595
30596     /**
30597      * Property: nativeJSON
30598      * {Boolean} Does the browser support native json?
30599      */
30600     nativeJSON: (function() {
30601         return !!(window.JSON && typeof JSON.parse == "function" && typeof JSON.stringify == "function");
30602     })(),
30603
30604     /**
30605      * Constructor: OpenLayers.Format.JSON
30606      * Create a new parser for JSON.
30607      *
30608      * Parameters:
30609      * options - {Object} An optional object whose properties will be set on
30610      *     this instance.
30611      */
30612
30613     /**
30614      * APIMethod: read
30615      * Deserialize a json string.
30616      *
30617      * Parameters:
30618      * json - {String} A JSON string
30619      * filter - {Function} A function which will be called for every key and
30620      *     value at every level of the final result. Each value will be
30621      *     replaced by the result of the filter function. This can be used to
30622      *     reform generic objects into instances of classes, or to transform
30623      *     date strings into Date objects.
30624      *     
30625      * Returns:
30626      * {Object} An object, array, string, or number .
30627      */
30628     read: function(json, filter) {
30629         var object;
30630         if (this.nativeJSON) {
30631             object = JSON.parse(json, filter);
30632         } else try {
30633             /**
30634              * Parsing happens in three stages. In the first stage, we run the
30635              *     text against a regular expression which looks for non-JSON
30636              *     characters. We are especially concerned with '()' and 'new'
30637              *     because they can cause invocation, and '=' because it can
30638              *     cause mutation. But just to be safe, we will reject all
30639              *     unexpected characters.
30640              */
30641             if (/^[\],:{}\s]*$/.test(json.replace(/\\["\\\/bfnrtu]/g, '@').
30642                                 replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
30643                                 replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
30644
30645                 /**
30646                  * In the second stage we use the eval function to compile the
30647                  *     text into a JavaScript structure. The '{' operator is
30648                  *     subject to a syntactic ambiguity in JavaScript - it can
30649                  *     begin a block or an object literal. We wrap the text in
30650                  *     parens to eliminate the ambiguity.
30651                  */
30652                 object = eval('(' + json + ')');
30653
30654                 /**
30655                  * In the optional third stage, we recursively walk the new
30656                  *     structure, passing each name/value pair to a filter
30657                  *     function for possible transformation.
30658                  */
30659                 if(typeof filter === 'function') {
30660                     function walk(k, v) {
30661                         if(v && typeof v === 'object') {
30662                             for(var i in v) {
30663                                 if(v.hasOwnProperty(i)) {
30664                                     v[i] = walk(i, v[i]);
30665                                 }
30666                             }
30667                         }
30668                         return filter(k, v);
30669                     }
30670                     object = walk('', object);
30671                 }
30672             }
30673         } catch(e) {
30674             // Fall through if the regexp test fails.
30675         }
30676
30677         if(this.keepData) {
30678             this.data = object;
30679         }
30680
30681         return object;
30682     },
30683
30684     /**
30685      * APIMethod: write
30686      * Serialize an object into a JSON string.
30687      *
30688      * Parameters:
30689      * value - {String} The object, array, string, number, boolean or date
30690      *     to be serialized.
30691      * pretty - {Boolean} Structure the output with newlines and indentation.
30692      *     Default is false.
30693      *
30694      * Returns:
30695      * {String} The JSON string representation of the input value.
30696      */
30697     write: function(value, pretty) {
30698         this.pretty = !!pretty;
30699         var json = null;
30700         var type = typeof value;
30701         if(this.serialize[type]) {
30702             try {
30703                 json = (!this.pretty && this.nativeJSON) ?
30704                     JSON.stringify(value) :
30705                     this.serialize[type].apply(this, [value]);
30706             } catch(err) {
30707                 OpenLayers.Console.error("Trouble serializing: " + err);
30708             }
30709         }
30710         return json;
30711     },
30712     
30713     /**
30714      * Method: writeIndent
30715      * Output an indentation string depending on the indentation level.
30716      *
30717      * Returns:
30718      * {String} An appropriate indentation string.
30719      */
30720     writeIndent: function() {
30721         var pieces = [];
30722         if(this.pretty) {
30723             for(var i=0; i<this.level; ++i) {
30724                 pieces.push(this.indent);
30725             }
30726         }
30727         return pieces.join('');
30728     },
30729     
30730     /**
30731      * Method: writeNewline
30732      * Output a string representing a newline if in pretty printing mode.
30733      *
30734      * Returns:
30735      * {String} A string representing a new line.
30736      */
30737     writeNewline: function() {
30738         return (this.pretty) ? this.newline : '';
30739     },
30740     
30741     /**
30742      * Method: writeSpace
30743      * Output a string representing a space if in pretty printing mode.
30744      *
30745      * Returns:
30746      * {String} A space.
30747      */
30748     writeSpace: function() {
30749         return (this.pretty) ? this.space : '';
30750     },
30751
30752     /**
30753      * Property: serialize
30754      * Object with properties corresponding to the serializable data types.
30755      *     Property values are functions that do the actual serializing.
30756      */
30757     serialize: {
30758         /**
30759          * Method: serialize.object
30760          * Transform an object into a JSON string.
30761          *
30762          * Parameters:
30763          * object - {Object} The object to be serialized.
30764          * 
30765          * Returns:
30766          * {String} A JSON string representing the object.
30767          */
30768         'object': function(object) {
30769             // three special objects that we want to treat differently
30770             if(object == null) {
30771                 return "null";
30772             }
30773             if(object.constructor == Date) {
30774                 return this.serialize.date.apply(this, [object]);
30775             }
30776             if(object.constructor == Array) {
30777                 return this.serialize.array.apply(this, [object]);
30778             }
30779             var pieces = ['{'];
30780             this.level += 1;
30781             var key, keyJSON, valueJSON;
30782             
30783             var addComma = false;
30784             for(key in object) {
30785                 if(object.hasOwnProperty(key)) {
30786                     // recursive calls need to allow for sub-classing
30787                     keyJSON = OpenLayers.Format.JSON.prototype.write.apply(this,
30788                                                     [key, this.pretty]);
30789                     valueJSON = OpenLayers.Format.JSON.prototype.write.apply(this,
30790                                                     [object[key], this.pretty]);
30791                     if(keyJSON != null && valueJSON != null) {
30792                         if(addComma) {
30793                             pieces.push(',');
30794                         }
30795                         pieces.push(this.writeNewline(), this.writeIndent(),
30796                                     keyJSON, ':', this.writeSpace(), valueJSON);
30797                         addComma = true;
30798                     }
30799                 }
30800             }
30801             
30802             this.level -= 1;
30803             pieces.push(this.writeNewline(), this.writeIndent(), '}');
30804             return pieces.join('');
30805         },
30806         
30807         /**
30808          * Method: serialize.array
30809          * Transform an array into a JSON string.
30810          *
30811          * Parameters:
30812          * array - {Array} The array to be serialized
30813          * 
30814          * Returns:
30815          * {String} A JSON string representing the array.
30816          */
30817         'array': function(array) {
30818             var json;
30819             var pieces = ['['];
30820             this.level += 1;
30821     
30822             for(var i=0, len=array.length; i<len; ++i) {
30823                 // recursive calls need to allow for sub-classing
30824                 json = OpenLayers.Format.JSON.prototype.write.apply(this,
30825                                                     [array[i], this.pretty]);
30826                 if(json != null) {
30827                     if(i > 0) {
30828                         pieces.push(',');
30829                     }
30830                     pieces.push(this.writeNewline(), this.writeIndent(), json);
30831                 }
30832             }
30833
30834             this.level -= 1;    
30835             pieces.push(this.writeNewline(), this.writeIndent(), ']');
30836             return pieces.join('');
30837         },
30838         
30839         /**
30840          * Method: serialize.string
30841          * Transform a string into a JSON string.
30842          *
30843          * Parameters:
30844          * string - {String} The string to be serialized
30845          * 
30846          * Returns:
30847          * {String} A JSON string representing the string.
30848          */
30849         'string': function(string) {
30850             // If the string contains no control characters, no quote characters, and no
30851             // backslash characters, then we can simply slap some quotes around it.
30852             // Otherwise we must also replace the offending characters with safe
30853             // sequences.    
30854             var m = {
30855                 '\b': '\\b',
30856                 '\t': '\\t',
30857                 '\n': '\\n',
30858                 '\f': '\\f',
30859                 '\r': '\\r',
30860                 '"' : '\\"',
30861                 '\\': '\\\\'
30862             };
30863             if(/["\\\x00-\x1f]/.test(string)) {
30864                 return '"' + string.replace(/([\x00-\x1f\\"])/g, function(a, b) {
30865                     var c = m[b];
30866                     if(c) {
30867                         return c;
30868                     }
30869                     c = b.charCodeAt();
30870                     return '\\u00' +
30871                         Math.floor(c / 16).toString(16) +
30872                         (c % 16).toString(16);
30873                 }) + '"';
30874             }
30875             return '"' + string + '"';
30876         },
30877
30878         /**
30879          * Method: serialize.number
30880          * Transform a number into a JSON string.
30881          *
30882          * Parameters:
30883          * number - {Number} The number to be serialized.
30884          *
30885          * Returns:
30886          * {String} A JSON string representing the number.
30887          */
30888         'number': function(number) {
30889             return isFinite(number) ? String(number) : "null";
30890         },
30891         
30892         /**
30893          * Method: serialize.boolean
30894          * Transform a boolean into a JSON string.
30895          *
30896          * Parameters:
30897          * bool - {Boolean} The boolean to be serialized.
30898          * 
30899          * Returns:
30900          * {String} A JSON string representing the boolean.
30901          */
30902         'boolean': function(bool) {
30903             return String(bool);
30904         },
30905         
30906         /**
30907          * Method: serialize.object
30908          * Transform a date into a JSON string.
30909          *
30910          * Parameters:
30911          * date - {Date} The date to be serialized.
30912          * 
30913          * Returns:
30914          * {String} A JSON string representing the date.
30915          */
30916         'date': function(date) {    
30917             function format(number) {
30918                 // Format integers to have at least two digits.
30919                 return (number < 10) ? '0' + number : number;
30920             }
30921             return '"' + date.getFullYear() + '-' +
30922                     format(date.getMonth() + 1) + '-' +
30923                     format(date.getDate()) + 'T' +
30924                     format(date.getHours()) + ':' +
30925                     format(date.getMinutes()) + ':' +
30926                     format(date.getSeconds()) + '"';
30927         }
30928     },
30929
30930     CLASS_NAME: "OpenLayers.Format.JSON" 
30931
30932 });     
30933 /* ======================================================================
30934     OpenLayers/Format/GeoJSON.js
30935    ====================================================================== */
30936
30937 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
30938  * full list of contributors). Published under the 2-clause BSD license.
30939  * See license.txt in the OpenLayers distribution or repository for the
30940  * full text of the license. */
30941
30942 /**
30943  * @requires OpenLayers/Format/JSON.js
30944  * @requires OpenLayers/Feature/Vector.js
30945  * @requires OpenLayers/Geometry/Point.js
30946  * @requires OpenLayers/Geometry/MultiPoint.js
30947  * @requires OpenLayers/Geometry/LineString.js
30948  * @requires OpenLayers/Geometry/MultiLineString.js
30949  * @requires OpenLayers/Geometry/Polygon.js
30950  * @requires OpenLayers/Geometry/MultiPolygon.js
30951  * @requires OpenLayers/Console.js
30952  */
30953
30954 /**
30955  * Class: OpenLayers.Format.GeoJSON
30956  * Read and write GeoJSON. Create a new parser with the
30957  *     <OpenLayers.Format.GeoJSON> constructor.
30958  *
30959  * Inherits from:
30960  *  - <OpenLayers.Format.JSON>
30961  */
30962 OpenLayers.Format.GeoJSON = OpenLayers.Class(OpenLayers.Format.JSON, {
30963
30964     /**
30965      * APIProperty: ignoreExtraDims
30966      * {Boolean} Ignore dimensions higher than 2 when reading geometry
30967      * coordinates.
30968      */ 
30969     ignoreExtraDims: false,
30970     
30971     /**
30972      * Constructor: OpenLayers.Format.GeoJSON
30973      * Create a new parser for GeoJSON.
30974      *
30975      * Parameters:
30976      * options - {Object} An optional object whose properties will be set on
30977      *     this instance.
30978      */
30979
30980     /**
30981      * APIMethod: read
30982      * Deserialize a GeoJSON string.
30983      *
30984      * Parameters:
30985      * json - {String} A GeoJSON string
30986      * type - {String} Optional string that determines the structure of
30987      *     the output.  Supported values are "Geometry", "Feature", and
30988      *     "FeatureCollection".  If absent or null, a default of
30989      *     "FeatureCollection" is assumed.
30990      * filter - {Function} A function which will be called for every key and
30991      *     value at every level of the final result. Each value will be
30992      *     replaced by the result of the filter function. This can be used to
30993      *     reform generic objects into instances of classes, or to transform
30994      *     date strings into Date objects.
30995      *
30996      * Returns: 
30997      * {Object} The return depends on the value of the type argument. If type
30998      *     is "FeatureCollection" (the default), the return will be an array
30999      *     of <OpenLayers.Feature.Vector>. If type is "Geometry", the input json
31000      *     must represent a single geometry, and the return will be an
31001      *     <OpenLayers.Geometry>.  If type is "Feature", the input json must
31002      *     represent a single feature, and the return will be an
31003      *     <OpenLayers.Feature.Vector>.
31004      */
31005     read: function(json, type, filter) {
31006         type = (type) ? type : "FeatureCollection";
31007         var results = null;
31008         var obj = null;
31009         if (typeof json == "string") {
31010             obj = OpenLayers.Format.JSON.prototype.read.apply(this,
31011                                                               [json, filter]);
31012         } else { 
31013             obj = json;
31014         }    
31015         if(!obj) {
31016             OpenLayers.Console.error("Bad JSON: " + json);
31017         } else if(typeof(obj.type) != "string") {
31018             OpenLayers.Console.error("Bad GeoJSON - no type: " + json);
31019         } else if(this.isValidType(obj, type)) {
31020             switch(type) {
31021                 case "Geometry":
31022                     try {
31023                         results = this.parseGeometry(obj);
31024                     } catch(err) {
31025                         OpenLayers.Console.error(err);
31026                     }
31027                     break;
31028                 case "Feature":
31029                     try {
31030                         results = this.parseFeature(obj);
31031                         results.type = "Feature";
31032                     } catch(err) {
31033                         OpenLayers.Console.error(err);
31034                     }
31035                     break;
31036                 case "FeatureCollection":
31037                     // for type FeatureCollection, we allow input to be any type
31038                     results = [];
31039                     switch(obj.type) {
31040                         case "Feature":
31041                             try {
31042                                 results.push(this.parseFeature(obj));
31043                             } catch(err) {
31044                                 results = null;
31045                                 OpenLayers.Console.error(err);
31046                             }
31047                             break;
31048                         case "FeatureCollection":
31049                             for(var i=0, len=obj.features.length; i<len; ++i) {
31050                                 try {
31051                                     results.push(this.parseFeature(obj.features[i]));
31052                                 } catch(err) {
31053                                     results = null;
31054                                     OpenLayers.Console.error(err);
31055                                 }
31056                             }
31057                             break;
31058                         default:
31059                             try {
31060                                 var geom = this.parseGeometry(obj);
31061                                 results.push(new OpenLayers.Feature.Vector(geom));
31062                             } catch(err) {
31063                                 results = null;
31064                                 OpenLayers.Console.error(err);
31065                             }
31066                     }
31067                 break;
31068             }
31069         }
31070         return results;
31071     },
31072     
31073     /**
31074      * Method: isValidType
31075      * Check if a GeoJSON object is a valid representative of the given type.
31076      *
31077      * Returns:
31078      * {Boolean} The object is valid GeoJSON object of the given type.
31079      */
31080     isValidType: function(obj, type) {
31081         var valid = false;
31082         switch(type) {
31083             case "Geometry":
31084                 if(OpenLayers.Util.indexOf(
31085                     ["Point", "MultiPoint", "LineString", "MultiLineString",
31086                      "Polygon", "MultiPolygon", "Box", "GeometryCollection"],
31087                     obj.type) == -1) {
31088                     // unsupported geometry type
31089                     OpenLayers.Console.error("Unsupported geometry type: " +
31090                                               obj.type);
31091                 } else {
31092                     valid = true;
31093                 }
31094                 break;
31095             case "FeatureCollection":
31096                 // allow for any type to be converted to a feature collection
31097                 valid = true;
31098                 break;
31099             default:
31100                 // for Feature types must match
31101                 if(obj.type == type) {
31102                     valid = true;
31103                 } else {
31104                     OpenLayers.Console.error("Cannot convert types from " +
31105                                               obj.type + " to " + type);
31106                 }
31107         }
31108         return valid;
31109     },
31110     
31111     /**
31112      * Method: parseFeature
31113      * Convert a feature object from GeoJSON into an
31114      *     <OpenLayers.Feature.Vector>.
31115      *
31116      * Parameters:
31117      * obj - {Object} An object created from a GeoJSON object
31118      *
31119      * Returns:
31120      * {<OpenLayers.Feature.Vector>} A feature.
31121      */
31122     parseFeature: function(obj) {
31123         var feature, geometry, attributes, bbox;
31124         attributes = (obj.properties) ? obj.properties : {};
31125         bbox = (obj.geometry && obj.geometry.bbox) || obj.bbox;
31126         try {
31127             geometry = this.parseGeometry(obj.geometry);
31128         } catch(err) {
31129             // deal with bad geometries
31130             throw err;
31131         }
31132         feature = new OpenLayers.Feature.Vector(geometry, attributes);
31133         if(bbox) {
31134             feature.bounds = OpenLayers.Bounds.fromArray(bbox);
31135         }
31136         if(obj.id) {
31137             feature.fid = obj.id;
31138         }
31139         return feature;
31140     },
31141     
31142     /**
31143      * Method: parseGeometry
31144      * Convert a geometry object from GeoJSON into an <OpenLayers.Geometry>.
31145      *
31146      * Parameters:
31147      * obj - {Object} An object created from a GeoJSON object
31148      *
31149      * Returns: 
31150      * {<OpenLayers.Geometry>} A geometry.
31151      */
31152     parseGeometry: function(obj) {
31153         if (obj == null) {
31154             return null;
31155         }
31156         var geometry, collection = false;
31157         if(obj.type == "GeometryCollection") {
31158             if(!(OpenLayers.Util.isArray(obj.geometries))) {
31159                 throw "GeometryCollection must have geometries array: " + obj;
31160             }
31161             var numGeom = obj.geometries.length;
31162             var components = new Array(numGeom);
31163             for(var i=0; i<numGeom; ++i) {
31164                 components[i] = this.parseGeometry.apply(
31165                     this, [obj.geometries[i]]
31166                 );
31167             }
31168             geometry = new OpenLayers.Geometry.Collection(components);
31169             collection = true;
31170         } else {
31171             if(!(OpenLayers.Util.isArray(obj.coordinates))) {
31172                 throw "Geometry must have coordinates array: " + obj;
31173             }
31174             if(!this.parseCoords[obj.type.toLowerCase()]) {
31175                 throw "Unsupported geometry type: " + obj.type;
31176             }
31177             try {
31178                 geometry = this.parseCoords[obj.type.toLowerCase()].apply(
31179                     this, [obj.coordinates]
31180                 );
31181             } catch(err) {
31182                 // deal with bad coordinates
31183                 throw err;
31184             }
31185         }
31186         // We don't reproject collections because the children are reprojected
31187         // for us when they are created.
31188         if (this.internalProjection && this.externalProjection && !collection) {
31189             geometry.transform(this.externalProjection, 
31190                                this.internalProjection); 
31191         }                       
31192         return geometry;
31193     },
31194     
31195     /**
31196      * Property: parseCoords
31197      * Object with properties corresponding to the GeoJSON geometry types.
31198      *     Property values are functions that do the actual parsing.
31199      */
31200     parseCoords: {
31201         /**
31202          * Method: parseCoords.point
31203          * Convert a coordinate array from GeoJSON into an
31204          *     <OpenLayers.Geometry>.
31205          *
31206          * Parameters:
31207          * array - {Object} The coordinates array from the GeoJSON fragment.
31208          *
31209          * Returns:
31210          * {<OpenLayers.Geometry>} A geometry.
31211          */
31212         "point": function(array) {
31213             if (this.ignoreExtraDims == false && 
31214                   array.length != 2) {
31215                     throw "Only 2D points are supported: " + array;
31216             }
31217             return new OpenLayers.Geometry.Point(array[0], array[1]);
31218         },
31219         
31220         /**
31221          * Method: parseCoords.multipoint
31222          * Convert a coordinate array from GeoJSON into an
31223          *     <OpenLayers.Geometry>.
31224          *
31225          * Parameters:
31226          * array - {Object} The coordinates array from the GeoJSON fragment.
31227          *
31228          * Returns:
31229          * {<OpenLayers.Geometry>} A geometry.
31230          */
31231         "multipoint": function(array) {
31232             var points = [];
31233             var p = null;
31234             for(var i=0, len=array.length; i<len; ++i) {
31235                 try {
31236                     p = this.parseCoords["point"].apply(this, [array[i]]);
31237                 } catch(err) {
31238                     throw err;
31239                 }
31240                 points.push(p);
31241             }
31242             return new OpenLayers.Geometry.MultiPoint(points);
31243         },
31244
31245         /**
31246          * Method: parseCoords.linestring
31247          * Convert a coordinate array from GeoJSON into an
31248          *     <OpenLayers.Geometry>.
31249          *
31250          * Parameters:
31251          * array - {Object} The coordinates array from the GeoJSON fragment.
31252          *
31253          * Returns:
31254          * {<OpenLayers.Geometry>} A geometry.
31255          */
31256         "linestring": function(array) {
31257             var points = [];
31258             var p = null;
31259             for(var i=0, len=array.length; i<len; ++i) {
31260                 try {
31261                     p = this.parseCoords["point"].apply(this, [array[i]]);
31262                 } catch(err) {
31263                     throw err;
31264                 }
31265                 points.push(p);
31266             }
31267             return new OpenLayers.Geometry.LineString(points);
31268         },
31269         
31270         /**
31271          * Method: parseCoords.multilinestring
31272          * Convert a coordinate array from GeoJSON into an
31273          *     <OpenLayers.Geometry>.
31274          *
31275          * Parameters:
31276          * array - {Object} The coordinates array from the GeoJSON fragment.
31277          *
31278          * Returns:
31279          * {<OpenLayers.Geometry>} A geometry.
31280          */
31281         "multilinestring": function(array) {
31282             var lines = [];
31283             var l = null;
31284             for(var i=0, len=array.length; i<len; ++i) {
31285                 try {
31286                     l = this.parseCoords["linestring"].apply(this, [array[i]]);
31287                 } catch(err) {
31288                     throw err;
31289                 }
31290                 lines.push(l);
31291             }
31292             return new OpenLayers.Geometry.MultiLineString(lines);
31293         },
31294         
31295         /**
31296          * Method: parseCoords.polygon
31297          * Convert a coordinate array from GeoJSON into an
31298          *     <OpenLayers.Geometry>.
31299          *
31300          * Returns:
31301          * {<OpenLayers.Geometry>} A geometry.
31302          */
31303         "polygon": function(array) {
31304             var rings = [];
31305             var r, l;
31306             for(var i=0, len=array.length; i<len; ++i) {
31307                 try {
31308                     l = this.parseCoords["linestring"].apply(this, [array[i]]);
31309                 } catch(err) {
31310                     throw err;
31311                 }
31312                 r = new OpenLayers.Geometry.LinearRing(l.components);
31313                 rings.push(r);
31314             }
31315             return new OpenLayers.Geometry.Polygon(rings);
31316         },
31317
31318         /**
31319          * Method: parseCoords.multipolygon
31320          * Convert a coordinate array from GeoJSON into an
31321          *     <OpenLayers.Geometry>.
31322          *
31323          * Parameters:
31324          * array - {Object} The coordinates array from the GeoJSON fragment.
31325          *
31326          * Returns:
31327          * {<OpenLayers.Geometry>} A geometry.
31328          */
31329         "multipolygon": function(array) {
31330             var polys = [];
31331             var p = null;
31332             for(var i=0, len=array.length; i<len; ++i) {
31333                 try {
31334                     p = this.parseCoords["polygon"].apply(this, [array[i]]);
31335                 } catch(err) {
31336                     throw err;
31337                 }
31338                 polys.push(p);
31339             }
31340             return new OpenLayers.Geometry.MultiPolygon(polys);
31341         },
31342
31343         /**
31344          * Method: parseCoords.box
31345          * Convert a coordinate array from GeoJSON into an
31346          *     <OpenLayers.Geometry>.
31347          *
31348          * Parameters:
31349          * array - {Object} The coordinates array from the GeoJSON fragment.
31350          *
31351          * Returns:
31352          * {<OpenLayers.Geometry>} A geometry.
31353          */
31354         "box": function(array) {
31355             if(array.length != 2) {
31356                 throw "GeoJSON box coordinates must have 2 elements";
31357             }
31358             return new OpenLayers.Geometry.Polygon([
31359                 new OpenLayers.Geometry.LinearRing([
31360                     new OpenLayers.Geometry.Point(array[0][0], array[0][1]),
31361                     new OpenLayers.Geometry.Point(array[1][0], array[0][1]),
31362                     new OpenLayers.Geometry.Point(array[1][0], array[1][1]),
31363                     new OpenLayers.Geometry.Point(array[0][0], array[1][1]),
31364                     new OpenLayers.Geometry.Point(array[0][0], array[0][1])
31365                 ])
31366             ]);
31367         }
31368
31369     },
31370
31371     /**
31372      * APIMethod: write
31373      * Serialize a feature, geometry, array of features into a GeoJSON string.
31374      *
31375      * Parameters:
31376      * obj - {Object} An <OpenLayers.Feature.Vector>, <OpenLayers.Geometry>,
31377      *     or an array of features.
31378      * pretty - {Boolean} Structure the output with newlines and indentation.
31379      *     Default is false.
31380      *
31381      * Returns:
31382      * {String} The GeoJSON string representation of the input geometry,
31383      *     features, or array of features.
31384      */
31385     write: function(obj, pretty) {
31386         var geojson = {
31387             "type": null
31388         };
31389         if(OpenLayers.Util.isArray(obj)) {
31390             geojson.type = "FeatureCollection";
31391             var numFeatures = obj.length;
31392             geojson.features = new Array(numFeatures);
31393             for(var i=0; i<numFeatures; ++i) {
31394                 var element = obj[i];
31395                 if(!element instanceof OpenLayers.Feature.Vector) {
31396                     var msg = "FeatureCollection only supports collections " +
31397                               "of features: " + element;
31398                     throw msg;
31399                 }
31400                 geojson.features[i] = this.extract.feature.apply(
31401                     this, [element]
31402                 );
31403             }
31404         } else if (obj.CLASS_NAME.indexOf("OpenLayers.Geometry") == 0) {
31405             geojson = this.extract.geometry.apply(this, [obj]);
31406         } else if (obj instanceof OpenLayers.Feature.Vector) {
31407             geojson = this.extract.feature.apply(this, [obj]);
31408             if(obj.layer && obj.layer.projection) {
31409                 geojson.crs = this.createCRSObject(obj);
31410             }
31411         }
31412         return OpenLayers.Format.JSON.prototype.write.apply(this,
31413                                                             [geojson, pretty]);
31414     },
31415
31416     /**
31417      * Method: createCRSObject
31418      * Create the CRS object for an object.
31419      *
31420      * Parameters:
31421      * object - {<OpenLayers.Feature.Vector>} 
31422      *
31423      * Returns:
31424      * {Object} An object which can be assigned to the crs property
31425      * of a GeoJSON object.
31426      */
31427     createCRSObject: function(object) {
31428        var proj = object.layer.projection.toString();
31429        var crs = {};
31430        if (proj.match(/epsg:/i)) {
31431            var code = parseInt(proj.substring(proj.indexOf(":") + 1));
31432            if (code == 4326) {
31433                crs = {
31434                    "type": "name",
31435                    "properties": {
31436                        "name": "urn:ogc:def:crs:OGC:1.3:CRS84"
31437                    }
31438                };
31439            } else {    
31440                crs = {
31441                    "type": "name",
31442                    "properties": {
31443                        "name": "EPSG:" + code
31444                    }
31445                };
31446            }    
31447        }
31448        return crs;
31449     },
31450     
31451     /**
31452      * Property: extract
31453      * Object with properties corresponding to the GeoJSON types.
31454      *     Property values are functions that do the actual value extraction.
31455      */
31456     extract: {
31457         /**
31458          * Method: extract.feature
31459          * Return a partial GeoJSON object representing a single feature.
31460          *
31461          * Parameters:
31462          * feature - {<OpenLayers.Feature.Vector>}
31463          *
31464          * Returns:
31465          * {Object} An object representing the point.
31466          */
31467         'feature': function(feature) {
31468             var geom = this.extract.geometry.apply(this, [feature.geometry]);
31469             var json = {
31470                 "type": "Feature",
31471                 "properties": feature.attributes,
31472                 "geometry": geom
31473             };
31474             if (feature.fid != null) {
31475                 json.id = feature.fid;
31476             }
31477             return json;
31478         },
31479         
31480         /**
31481          * Method: extract.geometry
31482          * Return a GeoJSON object representing a single geometry.
31483          *
31484          * Parameters:
31485          * geometry - {<OpenLayers.Geometry>}
31486          *
31487          * Returns:
31488          * {Object} An object representing the geometry.
31489          */
31490         'geometry': function(geometry) {
31491             if (geometry == null) {
31492                 return null;
31493             }
31494             if (this.internalProjection && this.externalProjection) {
31495                 geometry = geometry.clone();
31496                 geometry.transform(this.internalProjection, 
31497                                    this.externalProjection);
31498             }                       
31499             var geometryType = geometry.CLASS_NAME.split('.')[2];
31500             var data = this.extract[geometryType.toLowerCase()].apply(this, [geometry]);
31501             var json;
31502             if(geometryType == "Collection") {
31503                 json = {
31504                     "type": "GeometryCollection",
31505                     "geometries": data
31506                 };
31507             } else {
31508                 json = {
31509                     "type": geometryType,
31510                     "coordinates": data
31511                 };
31512             }
31513             
31514             return json;
31515         },
31516
31517         /**
31518          * Method: extract.point
31519          * Return an array of coordinates from a point.
31520          *
31521          * Parameters:
31522          * point - {<OpenLayers.Geometry.Point>}
31523          *
31524          * Returns: 
31525          * {Array} An array of coordinates representing the point.
31526          */
31527         'point': function(point) {
31528             return [point.x, point.y];
31529         },
31530
31531         /**
31532          * Method: extract.multipoint
31533          * Return an array of point coordinates from a multipoint.
31534          *
31535          * Parameters:
31536          * multipoint - {<OpenLayers.Geometry.MultiPoint>}
31537          *
31538          * Returns:
31539          * {Array} An array of point coordinate arrays representing
31540          *     the multipoint.
31541          */
31542         'multipoint': function(multipoint) {
31543             var array = [];
31544             for(var i=0, len=multipoint.components.length; i<len; ++i) {
31545                 array.push(this.extract.point.apply(this, [multipoint.components[i]]));
31546             }
31547             return array;
31548         },
31549         
31550         /**
31551          * Method: extract.linestring
31552          * Return an array of coordinate arrays from a linestring.
31553          *
31554          * Parameters:
31555          * linestring - {<OpenLayers.Geometry.LineString>}
31556          *
31557          * Returns:
31558          * {Array} An array of coordinate arrays representing
31559          *     the linestring.
31560          */
31561         'linestring': function(linestring) {
31562             var array = [];
31563             for(var i=0, len=linestring.components.length; i<len; ++i) {
31564                 array.push(this.extract.point.apply(this, [linestring.components[i]]));
31565             }
31566             return array;
31567         },
31568
31569         /**
31570          * Method: extract.multilinestring
31571          * Return an array of linestring arrays from a linestring.
31572          * 
31573          * Parameters:
31574          * multilinestring - {<OpenLayers.Geometry.MultiLineString>}
31575          * 
31576          * Returns:
31577          * {Array} An array of linestring arrays representing
31578          *     the multilinestring.
31579          */
31580         'multilinestring': function(multilinestring) {
31581             var array = [];
31582             for(var i=0, len=multilinestring.components.length; i<len; ++i) {
31583                 array.push(this.extract.linestring.apply(this, [multilinestring.components[i]]));
31584             }
31585             return array;
31586         },
31587         
31588         /**
31589          * Method: extract.polygon
31590          * Return an array of linear ring arrays from a polygon.
31591          *
31592          * Parameters:
31593          * polygon - {<OpenLayers.Geometry.Polygon>}
31594          * 
31595          * Returns:
31596          * {Array} An array of linear ring arrays representing the polygon.
31597          */
31598         'polygon': function(polygon) {
31599             var array = [];
31600             for(var i=0, len=polygon.components.length; i<len; ++i) {
31601                 array.push(this.extract.linestring.apply(this, [polygon.components[i]]));
31602             }
31603             return array;
31604         },
31605
31606         /**
31607          * Method: extract.multipolygon
31608          * Return an array of polygon arrays from a multipolygon.
31609          * 
31610          * Parameters:
31611          * multipolygon - {<OpenLayers.Geometry.MultiPolygon>}
31612          * 
31613          * Returns:
31614          * {Array} An array of polygon arrays representing
31615          *     the multipolygon
31616          */
31617         'multipolygon': function(multipolygon) {
31618             var array = [];
31619             for(var i=0, len=multipolygon.components.length; i<len; ++i) {
31620                 array.push(this.extract.polygon.apply(this, [multipolygon.components[i]]));
31621             }
31622             return array;
31623         },
31624         
31625         /**
31626          * Method: extract.collection
31627          * Return an array of geometries from a geometry collection.
31628          * 
31629          * Parameters:
31630          * collection - {<OpenLayers.Geometry.Collection>}
31631          * 
31632          * Returns:
31633          * {Array} An array of geometry objects representing the geometry
31634          *     collection.
31635          */
31636         'collection': function(collection) {
31637             var len = collection.components.length;
31638             var array = new Array(len);
31639             for(var i=0; i<len; ++i) {
31640                 array[i] = this.extract.geometry.apply(
31641                     this, [collection.components[i]]
31642                 );
31643             }
31644             return array;
31645         }
31646         
31647
31648     },
31649
31650     CLASS_NAME: "OpenLayers.Format.GeoJSON" 
31651
31652 });     
31653 /* ======================================================================
31654     OpenLayers/Lang/de.js
31655    ====================================================================== */
31656
31657 /* Translators (2009 onwards):
31658  *  - Grille chompa
31659  *  - Nikiwaibel
31660  *  - Umherirrender
31661  */
31662
31663 /**
31664  * @requires OpenLayers/Lang.js
31665  */
31666
31667 /**
31668  * Namespace: OpenLayers.Lang["de"]
31669  * Dictionary for Deutsch.  Keys for entries are used in calls to
31670  *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
31671  *     strings formatted for use with <OpenLayers.String.format> calls.
31672  */
31673 OpenLayers.Lang["de"] = OpenLayers.Util.applyDefaults({
31674
31675     'unhandledRequest': "Unbehandelte Anfragerückmeldung ${statusText}",
31676
31677     'Permalink': "Permalink",
31678
31679     'Overlays': "Overlays",
31680
31681     'Base Layer': "Grundkarte",
31682
31683     'noFID': "Ein Feature, für das keine FID existiert, kann nicht aktualisiert werden.",
31684
31685     'browserNotSupported': "Ihr Browser unterstützt keine Vektordarstellung. Aktuell unterstützte Renderer:\n${renderers}",
31686
31687     'minZoomLevelError': "Die \x3ccode\x3eminZoomLevel\x3c/code\x3e-Eigenschaft ist nur für die Verwendung mit \x3ccode\x3eFixedZoomLevels\x3c/code\x3e-untergeordneten Layers vorgesehen. Das dieser \x3ctt\x3ewfs\x3c/tt\x3e-Layer die \x3ccode\x3eminZoomLevel\x3c/code\x3e-Eigenschaft Ã¼berprüft ist ein Relikt der Vergangenheit. Wir können diese Ãœberprüfung nicht entfernen, ohne das OL basierende Applikationen nicht mehr funktionieren. Daher markieren wir es als veraltet - die \x3ccode\x3eminZoomLevel\x3c/code\x3e-Überprüfung wird in Version 3.0 entfernt werden. Bitte verwenden Sie stattdessen die Min-/Max-Lösung, wie sie unter http://trac.openlayers.org/wiki/SettingZoomLevels beschrieben ist.",
31688
31689     'commitSuccess': "WFS-Transaktion: Erfolgreich ${response}",
31690
31691     'commitFailed': "WFS-Transaktion: Fehlgeschlagen ${response}",
31692
31693     'googleWarning': "Der Google-Layer konnte nicht korrekt geladen werden.\x3cbr\x3e\x3cbr\x3eUm diese Meldung nicht mehr zu erhalten, wählen Sie einen anderen Hintergrundlayer aus dem LayerSwitcher in der rechten oberen Ecke.\x3cbr\x3e\x3cbr\x3eSehr wahrscheinlich tritt dieser Fehler auf, weil das Skript der Google-Maps-Bibliothek nicht eingebunden wurde oder keinen gültigen API-Schlüssel für Ihre URL enthält.\x3cbr\x3e\x3cbr\x3eEntwickler: Besuche \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3edas Wiki\x3c/a\x3e für Hilfe zum korrekten Einbinden des Google-Layers",
31694
31695     'getLayerWarning': "Der ${layerType}-Layer konnte nicht korrekt geladen werden.\x3cbr\x3e\x3cbr\x3eUm diese Meldung nicht mehr zu erhalten, wählen Sie einen anderen Hintergrundlayer aus dem LayerSwitcher in der rechten oberen Ecke.\x3cbr\x3e\x3cbr\x3eSehr wahrscheinlich tritt dieser Fehler auf, weil das Skript der \'${layerLib}\'-Bibliothek nicht eingebunden wurde.\x3cbr\x3e\x3cbr\x3eEntwickler: Besuche \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3edas Wiki\x3c/a\x3e für Hilfe zum korrekten Einbinden von Layern",
31696
31697     'Scale = 1 : ${scaleDenom}': "Maßstab = 1 : ${scaleDenom}",
31698
31699     'W': "W",
31700
31701     'E': "O",
31702
31703     'N': "N",
31704
31705     'S': "S",
31706
31707     'reprojectDeprecated': "Sie verwenden die â€žReproject“-Option des Layers ${layerName}. Diese Option ist veraltet: Sie wurde entwickelt um die Anzeige von Daten auf kommerziellen Basiskarten zu unterstützen, aber diese Funktion sollte jetzt durch Unterstützung der â€žSpherical Mercator“ erreicht werden. Weitere Informationen sind unter http://trac.openlayers.org/wiki/SphericalMercator verfügbar.",
31708
31709     'methodDeprecated': "Die Methode ist veraltet und wird in 3.0 entfernt. Bitte verwende stattdessen ${newMethod}."
31710
31711 });
31712 /* ======================================================================
31713     OpenLayers/Lang/en.js
31714    ====================================================================== */
31715
31716 /**
31717  * @requires OpenLayers/Lang.js
31718  */
31719
31720 /**
31721  * Namespace: OpenLayers.Lang["en"]
31722  * Dictionary for English.  Keys for entries are used in calls to
31723  *     <OpenLayers.Lang.translate>.  Entry bodies are normal strings or
31724  *     strings formatted for use with <OpenLayers.String.format> calls.
31725  */
31726 OpenLayers.Lang.en = {
31727
31728     'unhandledRequest': "Unhandled request return ${statusText}",
31729
31730     'Permalink': "Permalink",
31731
31732     'Overlays': "Overlays",
31733
31734     'Base Layer': "Base Layer",
31735
31736     'noFID': "Can't update a feature for which there is no FID.",
31737
31738     'browserNotSupported':
31739         "Your browser does not support vector rendering. Currently supported renderers are:\n${renderers}",
31740
31741     // console message
31742     'minZoomLevelError':
31743         "The minZoomLevel property is only intended for use " +
31744         "with the FixedZoomLevels-descendent layers. That this " +
31745         "wfs layer checks for minZoomLevel is a relic of the" +
31746         "past. We cannot, however, remove it without possibly " +
31747         "breaking OL based applications that may depend on it." +
31748         " Therefore we are deprecating it -- the minZoomLevel " +
31749         "check below will be removed at 3.0. Please instead " +
31750         "use min/max resolution setting as described here: " +
31751         "http://trac.openlayers.org/wiki/SettingZoomLevels",
31752
31753     'commitSuccess': "WFS Transaction: SUCCESS ${response}",
31754
31755     'commitFailed': "WFS Transaction: FAILED ${response}",
31756
31757     'googleWarning':
31758         "The Google Layer was unable to load correctly.<br><br>" +
31759         "To get rid of this message, select a new BaseLayer " +
31760         "in the layer switcher in the upper-right corner.<br><br>" +
31761         "Most likely, this is because the Google Maps library " +
31762         "script was either not included, or does not contain the " +
31763         "correct API key for your site.<br><br>" +
31764         "Developers: For help getting this working correctly, " +
31765         "<a href='http://trac.openlayers.org/wiki/Google' " +
31766         "target='_blank'>click here</a>",
31767
31768     'getLayerWarning':
31769         "The ${layerType} Layer was unable to load correctly.<br><br>" +
31770         "To get rid of this message, select a new BaseLayer " +
31771         "in the layer switcher in the upper-right corner.<br><br>" +
31772         "Most likely, this is because the ${layerLib} library " +
31773         "script was not correctly included.<br><br>" +
31774         "Developers: For help getting this working correctly, " +
31775         "<a href='http://trac.openlayers.org/wiki/${layerLib}' " +
31776         "target='_blank'>click here</a>",
31777
31778     'Scale = 1 : ${scaleDenom}': "Scale = 1 : ${scaleDenom}",
31779     
31780     //labels for the graticule control
31781     'W': 'W',
31782     'E': 'E',
31783     'N': 'N',
31784     'S': 'S',
31785     'Graticule': 'Graticule',
31786
31787     // console message
31788     'reprojectDeprecated':
31789         "You are using the 'reproject' option " +
31790         "on the ${layerName} layer. This option is deprecated: " +
31791         "its use was designed to support displaying data over commercial " + 
31792         "basemaps, but that functionality should now be achieved by using " +
31793         "Spherical Mercator support. More information is available from " +
31794         "http://trac.openlayers.org/wiki/SphericalMercator.",
31795
31796     // console message
31797     'methodDeprecated':
31798         "This method has been deprecated and will be removed in 3.0. " +
31799         "Please use ${newMethod} instead.",
31800
31801     // **** end ****
31802     'end': ''
31803     
31804 };
31805 /* ======================================================================
31806     OpenLayers/Popup.js
31807    ====================================================================== */
31808
31809 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
31810  * full list of contributors). Published under the 2-clause BSD license.
31811  * See license.txt in the OpenLayers distribution or repository for the
31812  * full text of the license. */
31813
31814 /**
31815  * @requires OpenLayers/BaseTypes/Class.js
31816  */
31817
31818
31819 /**
31820  * Class: OpenLayers.Popup
31821  * A popup is a small div that can opened and closed on the map.
31822  * Typically opened in response to clicking on a marker.  
31823  * See <OpenLayers.Marker>.  Popup's don't require their own
31824  * layer and are added the the map using the <OpenLayers.Map.addPopup>
31825  * method.
31826  *
31827  * Example:
31828  * (code)
31829  * popup = new OpenLayers.Popup("chicken", 
31830  *                    new OpenLayers.LonLat(5,40),
31831  *                    new OpenLayers.Size(200,200),
31832  *                    "example popup",
31833  *                    true);
31834  *       
31835  * map.addPopup(popup);
31836  * (end)
31837  */
31838 OpenLayers.Popup = OpenLayers.Class({
31839
31840     /** 
31841      * Property: events  
31842      * {<OpenLayers.Events>} custom event manager 
31843      */
31844     events: null,
31845     
31846     /** Property: id
31847      * {String} the unique identifier assigned to this popup.
31848      */
31849     id: "",
31850
31851     /** 
31852      * Property: lonlat 
31853      * {<OpenLayers.LonLat>} the position of this popup on the map
31854      */
31855     lonlat: null,
31856
31857     /** 
31858      * Property: div 
31859      * {DOMElement} the div that contains this popup.
31860      */
31861     div: null,
31862
31863     /** 
31864      * Property: contentSize 
31865      * {<OpenLayers.Size>} the width and height of the content.
31866      */
31867     contentSize: null,    
31868
31869     /** 
31870      * Property: size 
31871      * {<OpenLayers.Size>} the width and height of the popup.
31872      */
31873     size: null,    
31874
31875     /** 
31876      * Property: contentHTML 
31877      * {String} An HTML string for this popup to display.
31878      */
31879     contentHTML: null,
31880     
31881     /** 
31882      * Property: backgroundColor 
31883      * {String} the background color used by the popup.
31884      */
31885     backgroundColor: "",
31886     
31887     /** 
31888      * Property: opacity 
31889      * {float} the opacity of this popup (between 0.0 and 1.0)
31890      */
31891     opacity: "",
31892
31893     /** 
31894      * Property: border 
31895      * {String} the border size of the popup.  (eg 2px)
31896      */
31897     border: "",
31898     
31899     /** 
31900      * Property: contentDiv 
31901      * {DOMElement} a reference to the element that holds the content of
31902      *              the div.
31903      */
31904     contentDiv: null,
31905     
31906     /** 
31907      * Property: groupDiv 
31908      * {DOMElement} First and only child of 'div'. The group Div contains the
31909      *     'contentDiv' and the 'closeDiv'.
31910      */
31911     groupDiv: null,
31912
31913     /** 
31914      * Property: closeDiv
31915      * {DOMElement} the optional closer image
31916      */
31917     closeDiv: null,
31918
31919     /** 
31920      * APIProperty: autoSize
31921      * {Boolean} Resize the popup to auto-fit the contents.
31922      *     Default is false.
31923      */
31924     autoSize: false,
31925
31926     /**
31927      * APIProperty: minSize
31928      * {<OpenLayers.Size>} Minimum size allowed for the popup's contents.
31929      */
31930     minSize: null,
31931
31932     /**
31933      * APIProperty: maxSize
31934      * {<OpenLayers.Size>} Maximum size allowed for the popup's contents.
31935      */
31936     maxSize: null,
31937
31938     /** 
31939      * Property: displayClass
31940      * {String} The CSS class of the popup.
31941      */
31942     displayClass: "olPopup",
31943
31944     /** 
31945      * Property: contentDisplayClass
31946      * {String} The CSS class of the popup content div.
31947      */
31948     contentDisplayClass: "olPopupContent",
31949
31950     /** 
31951      * Property: padding 
31952      * {int or <OpenLayers.Bounds>} An extra opportunity to specify internal 
31953      *     padding of the content div inside the popup. This was originally
31954      *     confused with the css padding as specified in style.css's 
31955      *     'olPopupContent' class. We would like to get rid of this altogether,
31956      *     except that it does come in handy for the framed and anchoredbubble
31957      *     popups, who need to maintain yet another barrier between their 
31958      *     content and the outer border of the popup itself. 
31959      * 
31960      *     Note that in order to not break API, we must continue to support 
31961      *     this property being set as an integer. Really, though, we'd like to 
31962      *     have this specified as a Bounds object so that user can specify
31963      *     distinct left, top, right, bottom paddings. With the 3.0 release
31964      *     we can make this only a bounds.
31965      */
31966     padding: 0,
31967
31968     /** 
31969      * Property: disableFirefoxOverflowHack
31970      * {Boolean} The hack for overflow in Firefox causes all elements 
31971      *     to be re-drawn, which causes Flash elements to be 
31972      *     re-initialized, which is troublesome.
31973      *     With this property the hack can be disabled.
31974      */
31975     disableFirefoxOverflowHack: false,
31976
31977     /**
31978      * Method: fixPadding
31979      * To be removed in 3.0, this function merely helps us to deal with the 
31980      *     case where the user may have set an integer value for padding, 
31981      *     instead of an <OpenLayers.Bounds> object.
31982      */
31983     fixPadding: function() {
31984         if (typeof this.padding == "number") {
31985             this.padding = new OpenLayers.Bounds(
31986                 this.padding, this.padding, this.padding, this.padding
31987             );
31988         }
31989     },
31990
31991     /**
31992      * APIProperty: panMapIfOutOfView
31993      * {Boolean} When drawn, pan map such that the entire popup is visible in
31994      *     the current viewport (if necessary).
31995      *     Default is false.
31996      */
31997     panMapIfOutOfView: false,
31998     
31999     /**
32000      * APIProperty: keepInMap 
32001      * {Boolean} If panMapIfOutOfView is false, and this property is true, 
32002      *     contrain the popup such that it always fits in the available map
32003      *     space. By default, this is not set on the base class. If you are
32004      *     creating popups that are near map edges and not allowing pannning,
32005      *     and especially if you have a popup which has a
32006      *     fixedRelativePosition, setting this to false may be a smart thing to
32007      *     do. Subclasses may want to override this setting.
32008      *   
32009      *     Default is false.
32010      */
32011     keepInMap: false,
32012
32013     /**
32014      * APIProperty: closeOnMove
32015      * {Boolean} When map pans, close the popup.
32016      *     Default is false.
32017      */
32018     closeOnMove: false,
32019     
32020     /** 
32021      * Property: map 
32022      * {<OpenLayers.Map>} this gets set in Map.js when the popup is added to the map
32023      */
32024     map: null,
32025
32026     /** 
32027     * Constructor: OpenLayers.Popup
32028     * Create a popup.
32029     * 
32030     * Parameters: 
32031     * id - {String} a unqiue identifier for this popup.  If null is passed
32032     *               an identifier will be automatically generated. 
32033     * lonlat - {<OpenLayers.LonLat>}  The position on the map the popup will
32034     *                                 be shown.
32035     * contentSize - {<OpenLayers.Size>} The size of the content.
32036     * contentHTML - {String}          An HTML string to display inside the   
32037     *                                 popup.
32038     * closeBox - {Boolean}            Whether to display a close box inside
32039     *                                 the popup.
32040     * closeBoxCallback - {Function}   Function to be called on closeBox click.
32041     */
32042     initialize:function(id, lonlat, contentSize, contentHTML, closeBox, closeBoxCallback) {
32043         if (id == null) {
32044             id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
32045         }
32046
32047         this.id = id;
32048         this.lonlat = lonlat;
32049
32050         this.contentSize = (contentSize != null) ? contentSize 
32051                                   : new OpenLayers.Size(
32052                                                    OpenLayers.Popup.WIDTH,
32053                                                    OpenLayers.Popup.HEIGHT);
32054         if (contentHTML != null) { 
32055              this.contentHTML = contentHTML;
32056         }
32057         this.backgroundColor = OpenLayers.Popup.COLOR;
32058         this.opacity = OpenLayers.Popup.OPACITY;
32059         this.border = OpenLayers.Popup.BORDER;
32060
32061         this.div = OpenLayers.Util.createDiv(this.id, null, null, 
32062                                              null, null, null, "hidden");
32063         this.div.className = this.displayClass;
32064         
32065         var groupDivId = this.id + "_GroupDiv";
32066         this.groupDiv = OpenLayers.Util.createDiv(groupDivId, null, null, 
32067                                                     null, "relative", null,
32068                                                     "hidden");
32069
32070         var id = this.div.id + "_contentDiv";
32071         this.contentDiv = OpenLayers.Util.createDiv(id, null, this.contentSize.clone(), 
32072                                                     null, "relative");
32073         this.contentDiv.className = this.contentDisplayClass;
32074         this.groupDiv.appendChild(this.contentDiv);
32075         this.div.appendChild(this.groupDiv);
32076
32077         if (closeBox) {
32078             this.addCloseBox(closeBoxCallback);
32079         } 
32080
32081         this.registerEvents();
32082     },
32083
32084     /** 
32085      * Method: destroy
32086      * nullify references to prevent circular references and memory leaks
32087      */
32088     destroy: function() {
32089
32090         this.id = null;
32091         this.lonlat = null;
32092         this.size = null;
32093         this.contentHTML = null;
32094         
32095         this.backgroundColor = null;
32096         this.opacity = null;
32097         this.border = null;
32098         
32099         if (this.closeOnMove && this.map) {
32100             this.map.events.unregister("movestart", this, this.hide);
32101         }
32102
32103         this.events.destroy();
32104         this.events = null;
32105         
32106         if (this.closeDiv) {
32107             OpenLayers.Event.stopObservingElement(this.closeDiv); 
32108             this.groupDiv.removeChild(this.closeDiv);
32109         }
32110         this.closeDiv = null;
32111         
32112         this.div.removeChild(this.groupDiv);
32113         this.groupDiv = null;
32114
32115         if (this.map != null) {
32116             this.map.removePopup(this);
32117         }
32118         this.map = null;
32119         this.div = null;
32120         
32121         this.autoSize = null;
32122         this.minSize = null;
32123         this.maxSize = null;
32124         this.padding = null;
32125         this.panMapIfOutOfView = null;
32126     },
32127
32128     /** 
32129     * Method: draw
32130     * Constructs the elements that make up the popup.
32131     *
32132     * Parameters:
32133     * px - {<OpenLayers.Pixel>} the position the popup in pixels.
32134     * 
32135     * Returns:
32136     * {DOMElement} Reference to a div that contains the drawn popup
32137     */
32138     draw: function(px) {
32139         if (px == null) {
32140             if ((this.lonlat != null) && (this.map != null)) {
32141                 px = this.map.getLayerPxFromLonLat(this.lonlat);
32142             }
32143         }
32144
32145         // this assumes that this.map already exists, which is okay because 
32146         // this.draw is only called once the popup has been added to the map.
32147         if (this.closeOnMove) {
32148             this.map.events.register("movestart", this, this.hide);
32149         }
32150         
32151         //listen to movestart, moveend to disable overflow (FF bug)
32152         if (!this.disableFirefoxOverflowHack && OpenLayers.BROWSER_NAME == 'firefox') {
32153             this.map.events.register("movestart", this, function() {
32154                 var style = document.defaultView.getComputedStyle(
32155                     this.contentDiv, null
32156                 );
32157                 var currentOverflow = style.getPropertyValue("overflow");
32158                 if (currentOverflow != "hidden") {
32159                     this.contentDiv._oldOverflow = currentOverflow;
32160                     this.contentDiv.style.overflow = "hidden";
32161                 }
32162             });
32163             this.map.events.register("moveend", this, function() {
32164                 var oldOverflow = this.contentDiv._oldOverflow;
32165                 if (oldOverflow) {
32166                     this.contentDiv.style.overflow = oldOverflow;
32167                     this.contentDiv._oldOverflow = null;
32168                 }
32169             });
32170         }
32171
32172         this.moveTo(px);
32173         if (!this.autoSize && !this.size) {
32174             this.setSize(this.contentSize);
32175         }
32176         this.setBackgroundColor();
32177         this.setOpacity();
32178         this.setBorder();
32179         this.setContentHTML();
32180         
32181         if (this.panMapIfOutOfView) {
32182             this.panIntoView();
32183         }    
32184
32185         return this.div;
32186     },
32187
32188     /** 
32189      * Method: updatePosition
32190      * if the popup has a lonlat and its map members set, 
32191      * then have it move itself to its proper position
32192      */
32193     updatePosition: function() {
32194         if ((this.lonlat) && (this.map)) {
32195             var px = this.map.getLayerPxFromLonLat(this.lonlat);
32196             if (px) {
32197                 this.moveTo(px);           
32198             }    
32199         }
32200     },
32201
32202     /**
32203      * Method: moveTo
32204      * 
32205      * Parameters:
32206      * px - {<OpenLayers.Pixel>} the top and left position of the popup div. 
32207      */
32208     moveTo: function(px) {
32209         if ((px != null) && (this.div != null)) {
32210             this.div.style.left = px.x + "px";
32211             this.div.style.top = px.y + "px";
32212         }
32213     },
32214
32215     /**
32216      * Method: visible
32217      *
32218      * Returns:      
32219      * {Boolean} Boolean indicating whether or not the popup is visible
32220      */
32221     visible: function() {
32222         return OpenLayers.Element.visible(this.div);
32223     },
32224
32225     /**
32226      * Method: toggle
32227      * Toggles visibility of the popup.
32228      */
32229     toggle: function() {
32230         if (this.visible()) {
32231             this.hide();
32232         } else {
32233             this.show();
32234         }
32235     },
32236
32237     /**
32238      * Method: show
32239      * Makes the popup visible.
32240      */
32241     show: function() {
32242         this.div.style.display = '';
32243
32244         if (this.panMapIfOutOfView) {
32245             this.panIntoView();
32246         }    
32247     },
32248
32249     /**
32250      * Method: hide
32251      * Makes the popup invisible.
32252      */
32253     hide: function() {
32254         this.div.style.display = 'none';
32255     },
32256
32257     /**
32258      * Method: setSize
32259      * Used to adjust the size of the popup. 
32260      *
32261      * Parameters:
32262      * contentSize - {<OpenLayers.Size>} the new size for the popup's 
32263      *     contents div (in pixels).
32264      */
32265     setSize:function(contentSize) { 
32266         this.size = contentSize.clone(); 
32267         
32268         // if our contentDiv has a css 'padding' set on it by a stylesheet, we 
32269         //  must add that to the desired "size". 
32270         var contentDivPadding = this.getContentDivPadding();
32271         var wPadding = contentDivPadding.left + contentDivPadding.right;
32272         var hPadding = contentDivPadding.top + contentDivPadding.bottom;
32273
32274         // take into account the popup's 'padding' property
32275         this.fixPadding();
32276         wPadding += this.padding.left + this.padding.right;
32277         hPadding += this.padding.top + this.padding.bottom;
32278
32279         // make extra space for the close div
32280         if (this.closeDiv) {
32281             var closeDivWidth = parseInt(this.closeDiv.style.width);
32282             wPadding += closeDivWidth + contentDivPadding.right;
32283         }
32284
32285         //increase size of the main popup div to take into account the 
32286         // users's desired padding and close div.        
32287         this.size.w += wPadding;
32288         this.size.h += hPadding;
32289
32290         //now if our browser is IE, we need to actually make the contents 
32291         // div itself bigger to take its own padding into effect. this makes 
32292         // me want to shoot someone, but so it goes.
32293         if (OpenLayers.BROWSER_NAME == "msie") {
32294             this.contentSize.w += 
32295                 contentDivPadding.left + contentDivPadding.right;
32296             this.contentSize.h += 
32297                 contentDivPadding.bottom + contentDivPadding.top;
32298         }
32299
32300         if (this.div != null) {
32301             this.div.style.width = this.size.w + "px";
32302             this.div.style.height = this.size.h + "px";
32303         }
32304         if (this.contentDiv != null){
32305             this.contentDiv.style.width = contentSize.w + "px";
32306             this.contentDiv.style.height = contentSize.h + "px";
32307         }
32308     },  
32309
32310     /**
32311      * APIMethod: updateSize
32312      * Auto size the popup so that it precisely fits its contents (as 
32313      *     determined by this.contentDiv.innerHTML). Popup size will, of
32314      *     course, be limited by the available space on the current map
32315      */
32316     updateSize: function() {
32317         
32318         // determine actual render dimensions of the contents by putting its
32319         // contents into a fake contentDiv (for the CSS) and then measuring it
32320         var preparedHTML = "<div class='" + this.contentDisplayClass+ "'>" + 
32321             this.contentDiv.innerHTML + 
32322             "</div>";
32323  
32324         var containerElement = (this.map) ? this.map.div : document.body;
32325         var realSize = OpenLayers.Util.getRenderedDimensions(
32326             preparedHTML, null, {
32327                 displayClass: this.displayClass,
32328                 containerElement: containerElement
32329             }
32330         );
32331
32332         // is the "real" size of the div is safe to display in our map?
32333         var safeSize = this.getSafeContentSize(realSize);
32334
32335         var newSize = null;
32336         if (safeSize.equals(realSize)) {
32337             //real size of content is small enough to fit on the map, 
32338             // so we use real size.
32339             newSize = realSize;
32340
32341         } else {
32342
32343             // make a new 'size' object with the clipped dimensions 
32344             // set or null if not clipped.
32345             var fixedSize = {
32346                 w: (safeSize.w < realSize.w) ? safeSize.w : null,
32347                 h: (safeSize.h < realSize.h) ? safeSize.h : null
32348             };
32349         
32350             if (fixedSize.w && fixedSize.h) {
32351                 //content is too big in both directions, so we will use 
32352                 // max popup size (safeSize), knowing well that it will 
32353                 // overflow both ways.                
32354                 newSize = safeSize;
32355             } else {
32356                 //content is clipped in only one direction, so we need to 
32357                 // run getRenderedDimensions() again with a fixed dimension
32358                 var clippedSize = OpenLayers.Util.getRenderedDimensions(
32359                     preparedHTML, fixedSize, {
32360                         displayClass: this.contentDisplayClass,
32361                         containerElement: containerElement
32362                     }
32363                 );
32364                 
32365                 //if the clipped size is still the same as the safeSize, 
32366                 // that means that our content must be fixed in the 
32367                 // offending direction. If overflow is 'auto', this means 
32368                 // we are going to have a scrollbar for sure, so we must 
32369                 // adjust for that.
32370                 //
32371                 var currentOverflow = OpenLayers.Element.getStyle(
32372                     this.contentDiv, "overflow"
32373                 );
32374                 if ( (currentOverflow != "hidden") && 
32375                      (clippedSize.equals(safeSize)) ) {
32376                     var scrollBar = OpenLayers.Util.getScrollbarWidth();
32377                     if (fixedSize.w) {
32378                         clippedSize.h += scrollBar;
32379                     } else {
32380                         clippedSize.w += scrollBar;
32381                     }
32382                 }
32383                 
32384                 newSize = this.getSafeContentSize(clippedSize);
32385             }
32386         }                        
32387         this.setSize(newSize);     
32388     },    
32389
32390     /**
32391      * Method: setBackgroundColor
32392      * Sets the background color of the popup.
32393      *
32394      * Parameters:
32395      * color - {String} the background color.  eg "#FFBBBB"
32396      */
32397     setBackgroundColor:function(color) { 
32398         if (color != undefined) {
32399             this.backgroundColor = color; 
32400         }
32401         
32402         if (this.div != null) {
32403             this.div.style.backgroundColor = this.backgroundColor;
32404         }
32405     },  
32406     
32407     /**
32408      * Method: setOpacity
32409      * Sets the opacity of the popup.
32410      * 
32411      * Parameters:
32412      * opacity - {float} A value between 0.0 (transparent) and 1.0 (solid).   
32413      */
32414     setOpacity:function(opacity) { 
32415         if (opacity != undefined) {
32416             this.opacity = opacity; 
32417         }
32418         
32419         if (this.div != null) {
32420             // for Mozilla and Safari
32421             this.div.style.opacity = this.opacity;
32422
32423             // for IE
32424             this.div.style.filter = 'alpha(opacity=' + this.opacity*100 + ')';
32425         }
32426     },  
32427     
32428     /**
32429      * Method: setBorder
32430      * Sets the border style of the popup.
32431      *
32432      * Parameters:
32433      * border - {String} The border style value. eg 2px 
32434      */
32435     setBorder:function(border) { 
32436         if (border != undefined) {
32437             this.border = border;
32438         }
32439         
32440         if (this.div != null) {
32441             this.div.style.border = this.border;
32442         }
32443     },      
32444     
32445     /**
32446      * Method: setContentHTML
32447      * Allows the user to set the HTML content of the popup.
32448      *
32449      * Parameters:
32450      * contentHTML - {String} HTML for the div.
32451      */
32452     setContentHTML:function(contentHTML) {
32453
32454         if (contentHTML != null) {
32455             this.contentHTML = contentHTML;
32456         }
32457        
32458         if ((this.contentDiv != null) && 
32459             (this.contentHTML != null) &&
32460             (this.contentHTML != this.contentDiv.innerHTML)) {
32461        
32462             this.contentDiv.innerHTML = this.contentHTML;
32463        
32464             if (this.autoSize) {
32465                 
32466                 //if popup has images, listen for when they finish
32467                 // loading and resize accordingly
32468                 this.registerImageListeners();
32469
32470                 //auto size the popup to its current contents
32471                 this.updateSize();
32472             }
32473         }    
32474
32475     },
32476     
32477     /**
32478      * Method: registerImageListeners
32479      * Called when an image contained by the popup loaded. this function
32480      *     updates the popup size, then unregisters the image load listener.
32481      */   
32482     registerImageListeners: function() { 
32483
32484         // As the images load, this function will call updateSize() to 
32485         // resize the popup to fit the content div (which presumably is now
32486         // bigger than when the image was not loaded).
32487         // 
32488         // If the 'panMapIfOutOfView' property is set, we will pan the newly
32489         // resized popup back into view.
32490         // 
32491         // Note that this function, when called, will have 'popup' and 
32492         // 'img' properties in the context.
32493         //
32494         var onImgLoad = function() {
32495             if (this.popup.id === null) { // this.popup has been destroyed!
32496                 return;
32497             }
32498             this.popup.updateSize();
32499      
32500             if ( this.popup.visible() && this.popup.panMapIfOutOfView ) {
32501                 this.popup.panIntoView();
32502             }
32503
32504             OpenLayers.Event.stopObserving(
32505                 this.img, "load", this.img._onImgLoad
32506             );
32507     
32508         };
32509
32510         //cycle through the images and if their size is 0x0, that means that 
32511         // they haven't been loaded yet, so we attach the listener, which 
32512         // will fire when the images finish loading and will resize the 
32513         // popup accordingly to its new size.
32514         var images = this.contentDiv.getElementsByTagName("img");
32515         for (var i = 0, len = images.length; i < len; i++) {
32516             var img = images[i];
32517             if (img.width == 0 || img.height == 0) {
32518
32519                 var context = {
32520                     'popup': this,
32521                     'img': img
32522                 };
32523
32524                 //expando this function to the image itself before registering
32525                 // it. This way we can easily and properly unregister it.
32526                 img._onImgLoad = OpenLayers.Function.bind(onImgLoad, context);
32527
32528                 OpenLayers.Event.observe(img, 'load', img._onImgLoad);
32529             }    
32530         } 
32531     },
32532
32533     /**
32534      * APIMethod: getSafeContentSize
32535      * 
32536      * Parameters:
32537      * size - {<OpenLayers.Size>} Desired size to make the popup.
32538      * 
32539      * Returns:
32540      * {<OpenLayers.Size>} A size to make the popup which is neither smaller
32541      *     than the specified minimum size, nor bigger than the maximum 
32542      *     size (which is calculated relative to the size of the viewport).
32543      */
32544     getSafeContentSize: function(size) {
32545
32546         var safeContentSize = size.clone();
32547
32548         // if our contentDiv has a css 'padding' set on it by a stylesheet, we 
32549         //  must add that to the desired "size". 
32550         var contentDivPadding = this.getContentDivPadding();
32551         var wPadding = contentDivPadding.left + contentDivPadding.right;
32552         var hPadding = contentDivPadding.top + contentDivPadding.bottom;
32553
32554         // take into account the popup's 'padding' property
32555         this.fixPadding();
32556         wPadding += this.padding.left + this.padding.right;
32557         hPadding += this.padding.top + this.padding.bottom;
32558
32559         if (this.closeDiv) {
32560             var closeDivWidth = parseInt(this.closeDiv.style.width);
32561             wPadding += closeDivWidth + contentDivPadding.right;
32562         }
32563
32564         // prevent the popup from being smaller than a specified minimal size
32565         if (this.minSize) {
32566             safeContentSize.w = Math.max(safeContentSize.w, 
32567                 (this.minSize.w - wPadding));
32568             safeContentSize.h = Math.max(safeContentSize.h, 
32569                 (this.minSize.h - hPadding));
32570         }
32571
32572         // prevent the popup from being bigger than a specified maximum size
32573         if (this.maxSize) {
32574             safeContentSize.w = Math.min(safeContentSize.w, 
32575                 (this.maxSize.w - wPadding));
32576             safeContentSize.h = Math.min(safeContentSize.h, 
32577                 (this.maxSize.h - hPadding));
32578         }
32579         
32580         //make sure the desired size to set doesn't result in a popup that 
32581         // is bigger than the map's viewport.
32582         //
32583         if (this.map && this.map.size) {
32584             
32585             var extraX = 0, extraY = 0;
32586             if (this.keepInMap && !this.panMapIfOutOfView) {
32587                 var px = this.map.getPixelFromLonLat(this.lonlat);
32588                 switch (this.relativePosition) {
32589                     case "tr":
32590                         extraX = px.x;
32591                         extraY = this.map.size.h - px.y;
32592                         break;
32593                     case "tl":
32594                         extraX = this.map.size.w - px.x;
32595                         extraY = this.map.size.h - px.y;
32596                         break;
32597                     case "bl":
32598                         extraX = this.map.size.w - px.x;
32599                         extraY = px.y;
32600                         break;
32601                     case "br":
32602                         extraX = px.x;
32603                         extraY = px.y;
32604                         break;
32605                     default:    
32606                         extraX = px.x;
32607                         extraY = this.map.size.h - px.y;
32608                         break;
32609                 }
32610             }    
32611           
32612             var maxY = this.map.size.h - 
32613                 this.map.paddingForPopups.top - 
32614                 this.map.paddingForPopups.bottom - 
32615                 hPadding - extraY;
32616             
32617             var maxX = this.map.size.w - 
32618                 this.map.paddingForPopups.left - 
32619                 this.map.paddingForPopups.right - 
32620                 wPadding - extraX;
32621             
32622             safeContentSize.w = Math.min(safeContentSize.w, maxX);
32623             safeContentSize.h = Math.min(safeContentSize.h, maxY);
32624         }
32625         
32626         return safeContentSize;
32627     },
32628     
32629     /**
32630      * Method: getContentDivPadding
32631      * Glorious, oh glorious hack in order to determine the css 'padding' of 
32632      *     the contentDiv. IE/Opera return null here unless we actually add the 
32633      *     popup's main 'div' element (which contains contentDiv) to the DOM. 
32634      *     So we make it invisible and then add it to the document temporarily. 
32635      *
32636      *     Once we've taken the padding readings we need, we then remove it 
32637      *     from the DOM (it will actually get added to the DOM in 
32638      *     Map.js's addPopup)
32639      *
32640      * Returns:
32641      * {<OpenLayers.Bounds>}
32642      */
32643     getContentDivPadding: function() {
32644
32645         //use cached value if we have it
32646         var contentDivPadding = this._contentDivPadding;
32647         if (!contentDivPadding) {
32648
32649             if (this.div.parentNode == null) {
32650                 //make the div invisible and add it to the page        
32651                 this.div.style.display = "none";
32652                 document.body.appendChild(this.div);
32653             }
32654                     
32655             //read the padding settings from css, put them in an OL.Bounds        
32656             contentDivPadding = new OpenLayers.Bounds(
32657                 OpenLayers.Element.getStyle(this.contentDiv, "padding-left"),
32658                 OpenLayers.Element.getStyle(this.contentDiv, "padding-bottom"),
32659                 OpenLayers.Element.getStyle(this.contentDiv, "padding-right"),
32660                 OpenLayers.Element.getStyle(this.contentDiv, "padding-top")
32661             );
32662     
32663             //cache the value
32664             this._contentDivPadding = contentDivPadding;
32665
32666             if (this.div.parentNode == document.body) {
32667                 //remove the div from the page and make it visible again
32668                 document.body.removeChild(this.div);
32669                 this.div.style.display = "";
32670             }
32671         }
32672         return contentDivPadding;
32673     },
32674
32675     /**
32676      * Method: addCloseBox
32677      * 
32678      * Parameters:
32679      * callback - {Function} The callback to be called when the close button
32680      *     is clicked.
32681      */
32682     addCloseBox: function(callback) {
32683
32684         this.closeDiv = OpenLayers.Util.createDiv(
32685             this.id + "_close", null, {w: 17, h: 17}
32686         );
32687         this.closeDiv.className = "olPopupCloseBox"; 
32688         
32689         // use the content div's css padding to determine if we should
32690         //  padd the close div
32691         var contentDivPadding = this.getContentDivPadding();
32692          
32693         this.closeDiv.style.right = contentDivPadding.right + "px";
32694         this.closeDiv.style.top = contentDivPadding.top + "px";
32695         this.groupDiv.appendChild(this.closeDiv);
32696
32697         var closePopup = callback || function(e) {
32698             this.hide();
32699             OpenLayers.Event.stop(e);
32700         };
32701         OpenLayers.Event.observe(this.closeDiv, "touchend", 
32702                 OpenLayers.Function.bindAsEventListener(closePopup, this));
32703         OpenLayers.Event.observe(this.closeDiv, "click", 
32704                 OpenLayers.Function.bindAsEventListener(closePopup, this));
32705     },
32706
32707     /**
32708      * Method: panIntoView
32709      * Pans the map such that the popup is totaly viewable (if necessary)
32710      */
32711     panIntoView: function() {
32712         
32713         var mapSize = this.map.getSize();
32714     
32715         //start with the top left corner of the popup, in px, 
32716         // relative to the viewport
32717         var origTL = this.map.getViewPortPxFromLayerPx( new OpenLayers.Pixel(
32718             parseInt(this.div.style.left),
32719             parseInt(this.div.style.top)
32720         ));
32721         var newTL = origTL.clone();
32722     
32723         //new left (compare to margins, using this.size to calculate right)
32724         if (origTL.x < this.map.paddingForPopups.left) {
32725             newTL.x = this.map.paddingForPopups.left;
32726         } else 
32727         if ( (origTL.x + this.size.w) > (mapSize.w - this.map.paddingForPopups.right)) {
32728             newTL.x = mapSize.w - this.map.paddingForPopups.right - this.size.w;
32729         }
32730         
32731         //new top (compare to margins, using this.size to calculate bottom)
32732         if (origTL.y < this.map.paddingForPopups.top) {
32733             newTL.y = this.map.paddingForPopups.top;
32734         } else 
32735         if ( (origTL.y + this.size.h) > (mapSize.h - this.map.paddingForPopups.bottom)) {
32736             newTL.y = mapSize.h - this.map.paddingForPopups.bottom - this.size.h;
32737         }
32738         
32739         var dx = origTL.x - newTL.x;
32740         var dy = origTL.y - newTL.y;
32741         
32742         this.map.pan(dx, dy);
32743     },
32744
32745     /** 
32746      * Method: registerEvents
32747      * Registers events on the popup.
32748      *
32749      * Do this in a separate function so that subclasses can 
32750      *   choose to override it if they wish to deal differently
32751      *   with mouse events
32752      * 
32753      *   Note in the following handler functions that some special
32754      *    care is needed to deal correctly with mousing and popups. 
32755      *   
32756      *   Because the user might select the zoom-rectangle option and
32757      *    then drag it over a popup, we need a safe way to allow the
32758      *    mousemove and mouseup events to pass through the popup when
32759      *    they are initiated from outside. The same procedure is needed for
32760      *    touchmove and touchend events.
32761      * 
32762      *   Otherwise, we want to essentially kill the event propagation
32763      *    for all other events, though we have to do so carefully, 
32764      *    without disabling basic html functionality, like clicking on 
32765      *    hyperlinks or drag-selecting text.
32766      */
32767      registerEvents:function() {
32768         this.events = new OpenLayers.Events(this, this.div, null, true);
32769
32770         function onTouchstart(evt) {
32771             OpenLayers.Event.stop(evt, true);
32772         }
32773         this.events.on({
32774             "mousedown": this.onmousedown,
32775             "mousemove": this.onmousemove,
32776             "mouseup": this.onmouseup,
32777             "click": this.onclick,
32778             "mouseout": this.onmouseout,
32779             "dblclick": this.ondblclick,
32780             "touchstart": onTouchstart,
32781             scope: this
32782         });
32783         
32784      },
32785
32786     /** 
32787      * Method: onmousedown 
32788      * When mouse goes down within the popup, make a note of
32789      *   it locally, and then do not propagate the mousedown 
32790      *   (but do so safely so that user can select text inside)
32791      * 
32792      * Parameters:
32793      * evt - {Event} 
32794      */
32795     onmousedown: function (evt) {
32796         this.mousedown = true;
32797         OpenLayers.Event.stop(evt, true);
32798     },
32799
32800     /** 
32801      * Method: onmousemove
32802      * If the drag was started within the popup, then 
32803      *   do not propagate the mousemove (but do so safely
32804      *   so that user can select text inside)
32805      * 
32806      * Parameters:
32807      * evt - {Event} 
32808      */
32809     onmousemove: function (evt) {
32810         if (this.mousedown) {
32811             OpenLayers.Event.stop(evt, true);
32812         }
32813     },
32814
32815     /** 
32816      * Method: onmouseup
32817      * When mouse comes up within the popup, after going down 
32818      *   in it, reset the flag, and then (once again) do not 
32819      *   propagate the event, but do so safely so that user can 
32820      *   select text inside
32821      * 
32822      * Parameters:
32823      * evt - {Event} 
32824      */
32825     onmouseup: function (evt) {
32826         if (this.mousedown) {
32827             this.mousedown = false;
32828             OpenLayers.Event.stop(evt, true);
32829         }
32830     },
32831
32832     /**
32833      * Method: onclick
32834      * Ignore clicks, but allowing default browser handling
32835      * 
32836      * Parameters:
32837      * evt - {Event} 
32838      */
32839     onclick: function (evt) {
32840         OpenLayers.Event.stop(evt, true);
32841     },
32842
32843     /** 
32844      * Method: onmouseout
32845      * When mouse goes out of the popup set the flag to false so that
32846      *   if they let go and then drag back in, we won't be confused.
32847      * 
32848      * Parameters:
32849      * evt - {Event} 
32850      */
32851     onmouseout: function (evt) {
32852         this.mousedown = false;
32853     },
32854     
32855     /** 
32856      * Method: ondblclick
32857      * Ignore double-clicks, but allowing default browser handling
32858      * 
32859      * Parameters:
32860      * evt - {Event} 
32861      */
32862     ondblclick: function (evt) {
32863         OpenLayers.Event.stop(evt, true);
32864     },
32865
32866     CLASS_NAME: "OpenLayers.Popup"
32867 });
32868
32869 OpenLayers.Popup.WIDTH = 200;
32870 OpenLayers.Popup.HEIGHT = 200;
32871 OpenLayers.Popup.COLOR = "white";
32872 OpenLayers.Popup.OPACITY = 1;
32873 OpenLayers.Popup.BORDER = "0px";
32874 /* ======================================================================
32875     OpenLayers/Popup/Anchored.js
32876    ====================================================================== */
32877
32878 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
32879  * full list of contributors). Published under the 2-clause BSD license.
32880  * See license.txt in the OpenLayers distribution or repository for the
32881  * full text of the license. */
32882
32883
32884 /**
32885  * @requires OpenLayers/Popup.js
32886  */
32887
32888 /**
32889  * Class: OpenLayers.Popup.Anchored
32890  * 
32891  * Inherits from:
32892  *  - <OpenLayers.Popup>
32893  */
32894 OpenLayers.Popup.Anchored = 
32895   OpenLayers.Class(OpenLayers.Popup, {
32896
32897     /** 
32898      * Property: relativePosition
32899      * {String} Relative position of the popup ("br", "tr", "tl" or "bl").
32900      */
32901     relativePosition: null,
32902     
32903     /**
32904      * APIProperty: keepInMap 
32905      * {Boolean} If panMapIfOutOfView is false, and this property is true, 
32906      *     contrain the popup such that it always fits in the available map
32907      *     space. By default, this is set. If you are creating popups that are
32908      *     near map edges and not allowing pannning, and especially if you have
32909      *     a popup which has a fixedRelativePosition, setting this to false may
32910      *     be a smart thing to do.
32911      *   
32912      *     For anchored popups, default is true, since subclasses will
32913      *     usually want this functionality.
32914      */
32915     keepInMap: true,
32916
32917     /**
32918      * Property: anchor
32919      * {Object} Object to which we'll anchor the popup. Must expose a 
32920      *     'size' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>).
32921      */
32922     anchor: null,
32923
32924     /** 
32925     * Constructor: OpenLayers.Popup.Anchored
32926     * 
32927     * Parameters:
32928     * id - {String}
32929     * lonlat - {<OpenLayers.LonLat>}
32930     * contentSize - {<OpenLayers.Size>}
32931     * contentHTML - {String}
32932     * anchor - {Object} Object which must expose a 'size' <OpenLayers.Size> 
32933     *     and 'offset' <OpenLayers.Pixel> (generally an <OpenLayers.Icon>).
32934     * closeBox - {Boolean}
32935     * closeBoxCallback - {Function} Function to be called on closeBox click.
32936     */
32937     initialize:function(id, lonlat, contentSize, contentHTML, anchor, closeBox,
32938                         closeBoxCallback) {
32939         var newArguments = [
32940             id, lonlat, contentSize, contentHTML, closeBox, closeBoxCallback
32941         ];
32942         OpenLayers.Popup.prototype.initialize.apply(this, newArguments);
32943
32944         this.anchor = (anchor != null) ? anchor 
32945                                        : { size: new OpenLayers.Size(0,0),
32946                                            offset: new OpenLayers.Pixel(0,0)};
32947     },
32948
32949     /**
32950      * APIMethod: destroy
32951      */
32952     destroy: function() {
32953         this.anchor = null;
32954         this.relativePosition = null;
32955         
32956         OpenLayers.Popup.prototype.destroy.apply(this, arguments);        
32957     },
32958
32959     /**
32960      * APIMethod: show
32961      * Overridden from Popup since user might hide popup and then show() it 
32962      *     in a new location (meaning we might want to update the relative
32963      *     position on the show)
32964      */
32965     show: function() {
32966         this.updatePosition();
32967         OpenLayers.Popup.prototype.show.apply(this, arguments);
32968     },
32969
32970     /**
32971      * Method: moveTo
32972      * Since the popup is moving to a new px, it might need also to be moved
32973      *     relative to where the marker is. We first calculate the new 
32974      *     relativePosition, and then we calculate the new px where we will 
32975      *     put the popup, based on the new relative position. 
32976      * 
32977      *     If the relativePosition has changed, we must also call 
32978      *     updateRelativePosition() to make any visual changes to the popup 
32979      *     which are associated with putting it in a new relativePosition.
32980      * 
32981      * Parameters:
32982      * px - {<OpenLayers.Pixel>}
32983      */
32984     moveTo: function(px) {
32985         var oldRelativePosition = this.relativePosition;
32986         this.relativePosition = this.calculateRelativePosition(px);
32987
32988         OpenLayers.Popup.prototype.moveTo.call(this, this.calculateNewPx(px));
32989         
32990         //if this move has caused the popup to change its relative position, 
32991         // we need to make the appropriate cosmetic changes.
32992         if (this.relativePosition != oldRelativePosition) {
32993             this.updateRelativePosition();
32994         }
32995     },
32996
32997     /**
32998      * APIMethod: setSize
32999      * 
33000      * Parameters:
33001      * contentSize - {<OpenLayers.Size>} the new size for the popup's 
33002      *     contents div (in pixels).
33003      */
33004     setSize:function(contentSize) { 
33005         OpenLayers.Popup.prototype.setSize.apply(this, arguments);
33006
33007         if ((this.lonlat) && (this.map)) {
33008             var px = this.map.getLayerPxFromLonLat(this.lonlat);
33009             this.moveTo(px);
33010         }
33011     },  
33012     
33013     /** 
33014      * Method: calculateRelativePosition
33015      * 
33016      * Parameters:
33017      * px - {<OpenLayers.Pixel>}
33018      * 
33019      * Returns:
33020      * {String} The relative position ("br" "tr" "tl" "bl") at which the popup
33021      *     should be placed.
33022      */
33023     calculateRelativePosition:function(px) {
33024         var lonlat = this.map.getLonLatFromLayerPx(px);        
33025         
33026         var extent = this.map.getExtent();
33027         var quadrant = extent.determineQuadrant(lonlat);
33028         
33029         return OpenLayers.Bounds.oppositeQuadrant(quadrant);
33030     }, 
33031
33032     /**
33033      * Method: updateRelativePosition
33034      * The popup has been moved to a new relative location, so we may want to 
33035      *     make some cosmetic adjustments to it. 
33036      * 
33037      *     Note that in the classic Anchored popup, there is nothing to do 
33038      *     here, since the popup looks exactly the same in all four positions.
33039      *     Subclasses such as Framed, however, will want to do something
33040      *     special here.
33041      */
33042     updateRelativePosition: function() {
33043         //to be overridden by subclasses
33044     },
33045
33046     /** 
33047      * Method: calculateNewPx
33048      * 
33049      * Parameters:
33050      * px - {<OpenLayers.Pixel>}
33051      * 
33052      * Returns:
33053      * {<OpenLayers.Pixel>} The the new px position of the popup on the screen
33054      *     relative to the passed-in px.
33055      */
33056     calculateNewPx:function(px) {
33057         var newPx = px.offset(this.anchor.offset);
33058         
33059         //use contentSize if size is not already set
33060         var size = this.size || this.contentSize;
33061
33062         var top = (this.relativePosition.charAt(0) == 't');
33063         newPx.y += (top) ? -size.h : this.anchor.size.h;
33064         
33065         var left = (this.relativePosition.charAt(1) == 'l');
33066         newPx.x += (left) ? -size.w : this.anchor.size.w;
33067
33068         return newPx;   
33069     },
33070
33071     CLASS_NAME: "OpenLayers.Popup.Anchored"
33072 });
33073 /* ======================================================================
33074     OpenLayers/Popup/Framed.js
33075    ====================================================================== */
33076
33077 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
33078  * full list of contributors). Published under the 2-clause BSD license.
33079  * See license.txt in the OpenLayers distribution or repository for the
33080  * full text of the license. */
33081
33082 /**
33083  * @requires OpenLayers/Popup/Anchored.js
33084  */
33085
33086 /**
33087  * Class: OpenLayers.Popup.Framed
33088  * 
33089  * Inherits from:
33090  *  - <OpenLayers.Popup.Anchored>
33091  */
33092 OpenLayers.Popup.Framed =
33093   OpenLayers.Class(OpenLayers.Popup.Anchored, {
33094
33095     /**
33096      * Property: imageSrc
33097      * {String} location of the image to be used as the popup frame
33098      */
33099     imageSrc: null,
33100
33101     /**
33102      * Property: imageSize
33103      * {<OpenLayers.Size>} Size (measured in pixels) of the image located
33104      *     by the 'imageSrc' property.
33105      */
33106     imageSize: null,
33107
33108     /**
33109      * APIProperty: isAlphaImage
33110      * {Boolean} The image has some alpha and thus needs to use the alpha 
33111      *     image hack. Note that setting this to true will have no noticeable
33112      *     effect in FF or IE7 browsers, but will all but crush the ie6 
33113      *     browser. 
33114      *     Default is false.
33115      */
33116     isAlphaImage: false,
33117
33118     /**
33119      * Property: positionBlocks
33120      * {Object} Hash of different position blocks (Object/Hashs). Each block 
33121      *     will be keyed by a two-character 'relativePosition' 
33122      *     code string (ie "tl", "tr", "bl", "br"). Block properties are 
33123      *     'offset', 'padding' (self-explanatory), and finally the 'blocks'
33124      *     parameter, which is an array of the block objects. 
33125      * 
33126      *     Each block object must have 'size', 'anchor', and 'position' 
33127      *     properties.
33128      * 
33129      *     Note that positionBlocks should never be modified at runtime.
33130      */
33131     positionBlocks: null,
33132
33133     /**
33134      * Property: blocks
33135      * {Array[Object]} Array of objects, each of which is one "block" of the 
33136      *     popup. Each block has a 'div' and an 'image' property, both of 
33137      *     which are DOMElements, and the latter of which is appended to the 
33138      *     former. These are reused as the popup goes changing positions for
33139      *     great economy and elegance.
33140      */
33141     blocks: null,
33142
33143     /** 
33144      * APIProperty: fixedRelativePosition
33145      * {Boolean} We want the framed popup to work dynamically placed relative
33146      *     to its anchor but also in just one fixed position. A well designed
33147      *     framed popup will have the pixels and logic to display itself in 
33148      *     any of the four relative positions, but (understandably), this will
33149      *     not be the case for all of them. By setting this property to 'true', 
33150      *     framed popup will not recalculate for the best placement each time
33151      *     it's open, but will always open the same way. 
33152      *     Note that if this is set to true, it is generally advisable to also
33153      *     set the 'panIntoView' property to true so that the popup can be 
33154      *     scrolled into view (since it will often be offscreen on open)
33155      *     Default is false.
33156      */
33157     fixedRelativePosition: false,
33158
33159     /** 
33160      * Constructor: OpenLayers.Popup.Framed
33161      * 
33162      * Parameters:
33163      * id - {String}
33164      * lonlat - {<OpenLayers.LonLat>}
33165      * contentSize - {<OpenLayers.Size>}
33166      * contentHTML - {String}
33167      * anchor - {Object} Object to which we'll anchor the popup. Must expose 
33168      *     a 'size' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>) 
33169      *     (Note that this is generally an <OpenLayers.Icon>).
33170      * closeBox - {Boolean}
33171      * closeBoxCallback - {Function} Function to be called on closeBox click.
33172      */
33173     initialize:function(id, lonlat, contentSize, contentHTML, anchor, closeBox, 
33174                         closeBoxCallback) {
33175
33176         OpenLayers.Popup.Anchored.prototype.initialize.apply(this, arguments);
33177
33178         if (this.fixedRelativePosition) {
33179             //based on our decided relativePostion, set the current padding
33180             // this keeps us from getting into trouble 
33181             this.updateRelativePosition();
33182             
33183             //make calculateRelativePosition always return the specified
33184             // fixed position.
33185             this.calculateRelativePosition = function(px) {
33186                 return this.relativePosition;
33187             };
33188         }
33189
33190         this.contentDiv.style.position = "absolute";
33191         this.contentDiv.style.zIndex = 1;
33192
33193         if (closeBox) {
33194             this.closeDiv.style.zIndex = 1;
33195         }
33196
33197         this.groupDiv.style.position = "absolute";
33198         this.groupDiv.style.top = "0px";
33199         this.groupDiv.style.left = "0px";
33200         this.groupDiv.style.height = "100%";
33201         this.groupDiv.style.width = "100%";
33202     },
33203
33204     /** 
33205      * APIMethod: destroy
33206      */
33207     destroy: function() {
33208         this.imageSrc = null;
33209         this.imageSize = null;
33210         this.isAlphaImage = null;
33211
33212         this.fixedRelativePosition = false;
33213         this.positionBlocks = null;
33214
33215         //remove our blocks
33216         for(var i = 0; i < this.blocks.length; i++) {
33217             var block = this.blocks[i];
33218
33219             if (block.image) {
33220                 block.div.removeChild(block.image);
33221             }
33222             block.image = null;
33223
33224             if (block.div) {
33225                 this.groupDiv.removeChild(block.div);
33226             }
33227             block.div = null;
33228         }
33229         this.blocks = null;
33230
33231         OpenLayers.Popup.Anchored.prototype.destroy.apply(this, arguments);
33232     },
33233
33234     /**
33235      * APIMethod: setBackgroundColor
33236      */
33237     setBackgroundColor:function(color) {
33238         //does nothing since the framed popup's entire scheme is based on a 
33239         // an image -- changing the background color makes no sense. 
33240     },
33241
33242     /**
33243      * APIMethod: setBorder
33244      */
33245     setBorder:function() {
33246         //does nothing since the framed popup's entire scheme is based on a 
33247         // an image -- changing the popup's border makes no sense. 
33248     },
33249
33250     /**
33251      * Method: setOpacity
33252      * Sets the opacity of the popup.
33253      * 
33254      * Parameters:
33255      * opacity - {float} A value between 0.0 (transparent) and 1.0 (solid).   
33256      */
33257     setOpacity:function(opacity) {
33258         //does nothing since we suppose that we'll never apply an opacity
33259         // to a framed popup
33260     },
33261
33262     /**
33263      * APIMethod: setSize
33264      * Overridden here, because we need to update the blocks whenever the size
33265      *     of the popup has changed.
33266      * 
33267      * Parameters:
33268      * contentSize - {<OpenLayers.Size>} the new size for the popup's 
33269      *     contents div (in pixels).
33270      */
33271     setSize:function(contentSize) { 
33272         OpenLayers.Popup.Anchored.prototype.setSize.apply(this, arguments);
33273
33274         this.updateBlocks();
33275     },
33276
33277     /**
33278      * Method: updateRelativePosition
33279      * When the relative position changes, we need to set the new padding 
33280      *     BBOX on the popup, reposition the close div, and update the blocks.
33281      */
33282     updateRelativePosition: function() {
33283
33284         //update the padding
33285         this.padding = this.positionBlocks[this.relativePosition].padding;
33286
33287         //update the position of our close box to new padding
33288         if (this.closeDiv) {
33289             // use the content div's css padding to determine if we should
33290             //  padd the close div
33291             var contentDivPadding = this.getContentDivPadding();
33292
33293             this.closeDiv.style.right = contentDivPadding.right + 
33294                                         this.padding.right + "px";
33295             this.closeDiv.style.top = contentDivPadding.top + 
33296                                       this.padding.top + "px";
33297         }
33298
33299         this.updateBlocks();
33300     },
33301
33302     /** 
33303      * Method: calculateNewPx
33304      * Besides the standard offset as determined by the Anchored class, our 
33305      *     Framed popups have a special 'offset' property for each of their 
33306      *     positions, which is used to offset the popup relative to its anchor.
33307      * 
33308      * Parameters:
33309      * px - {<OpenLayers.Pixel>}
33310      * 
33311      * Returns:
33312      * {<OpenLayers.Pixel>} The the new px position of the popup on the screen
33313      *     relative to the passed-in px.
33314      */
33315     calculateNewPx:function(px) {
33316         var newPx = OpenLayers.Popup.Anchored.prototype.calculateNewPx.apply(
33317             this, arguments
33318         );
33319
33320         newPx = newPx.offset(this.positionBlocks[this.relativePosition].offset);
33321
33322         return newPx;
33323     },
33324
33325     /**
33326      * Method: createBlocks
33327      */
33328     createBlocks: function() {
33329         this.blocks = [];
33330
33331         //since all positions contain the same number of blocks, we can 
33332         // just pick the first position and use its blocks array to create
33333         // our blocks array
33334         var firstPosition = null;
33335         for(var key in this.positionBlocks) {
33336             firstPosition = key;
33337             break;
33338         }
33339         
33340         var position = this.positionBlocks[firstPosition];
33341         for (var i = 0; i < position.blocks.length; i++) {
33342
33343             var block = {};
33344             this.blocks.push(block);
33345
33346             var divId = this.id + '_FrameDecorationDiv_' + i;
33347             block.div = OpenLayers.Util.createDiv(divId, 
33348                 null, null, null, "absolute", null, "hidden", null
33349             );
33350
33351             var imgId = this.id + '_FrameDecorationImg_' + i;
33352             var imageCreator = 
33353                 (this.isAlphaImage) ? OpenLayers.Util.createAlphaImageDiv
33354                                     : OpenLayers.Util.createImage;
33355
33356             block.image = imageCreator(imgId, 
33357                 null, this.imageSize, this.imageSrc, 
33358                 "absolute", null, null, null
33359             );
33360
33361             block.div.appendChild(block.image);
33362             this.groupDiv.appendChild(block.div);
33363         }
33364     },
33365
33366     /**
33367      * Method: updateBlocks
33368      * Internal method, called on initialize and when the popup's relative
33369      *     position has changed. This function takes care of re-positioning
33370      *     the popup's blocks in their appropropriate places.
33371      */
33372     updateBlocks: function() {
33373         if (!this.blocks) {
33374             this.createBlocks();
33375         }
33376         
33377         if (this.size && this.relativePosition) {
33378             var position = this.positionBlocks[this.relativePosition];
33379             for (var i = 0; i < position.blocks.length; i++) {
33380     
33381                 var positionBlock = position.blocks[i];
33382                 var block = this.blocks[i];
33383     
33384                 // adjust sizes
33385                 var l = positionBlock.anchor.left;
33386                 var b = positionBlock.anchor.bottom;
33387                 var r = positionBlock.anchor.right;
33388                 var t = positionBlock.anchor.top;
33389     
33390                 //note that we use the isNaN() test here because if the 
33391                 // size object is initialized with a "auto" parameter, the 
33392                 // size constructor calls parseFloat() on the string, 
33393                 // which will turn it into NaN
33394                 //
33395                 var w = (isNaN(positionBlock.size.w)) ? this.size.w - (r + l) 
33396                                                       : positionBlock.size.w;
33397     
33398                 var h = (isNaN(positionBlock.size.h)) ? this.size.h - (b + t) 
33399                                                       : positionBlock.size.h;
33400     
33401                 block.div.style.width = (w < 0 ? 0 : w) + 'px';
33402                 block.div.style.height = (h < 0 ? 0 : h) + 'px';
33403     
33404                 block.div.style.left = (l != null) ? l + 'px' : '';
33405                 block.div.style.bottom = (b != null) ? b + 'px' : '';
33406                 block.div.style.right = (r != null) ? r + 'px' : '';            
33407                 block.div.style.top = (t != null) ? t + 'px' : '';
33408     
33409                 block.image.style.left = positionBlock.position.x + 'px';
33410                 block.image.style.top = positionBlock.position.y + 'px';
33411             }
33412     
33413             this.contentDiv.style.left = this.padding.left + "px";
33414             this.contentDiv.style.top = this.padding.top + "px";
33415         }
33416     },
33417
33418     CLASS_NAME: "OpenLayers.Popup.Framed"
33419 });
33420 /* ======================================================================
33421     OpenLayers/Popup/FramedCloud.js
33422    ====================================================================== */
33423
33424 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
33425  * full list of contributors). Published under the 2-clause BSD license.
33426  * See license.txt in the OpenLayers distribution or repository for the
33427  * full text of the license. */
33428
33429 /**
33430  * @requires OpenLayers/Popup/Framed.js
33431  * @requires OpenLayers/Util.js
33432  * @requires OpenLayers/BaseTypes/Bounds.js
33433  * @requires OpenLayers/BaseTypes/Pixel.js
33434  * @requires OpenLayers/BaseTypes/Size.js
33435  */
33436
33437 /**
33438  * Class: OpenLayers.Popup.FramedCloud
33439  * 
33440  * Inherits from: 
33441  *  - <OpenLayers.Popup.Framed>
33442  */
33443 OpenLayers.Popup.FramedCloud = 
33444   OpenLayers.Class(OpenLayers.Popup.Framed, {
33445
33446     /** 
33447      * Property: contentDisplayClass
33448      * {String} The CSS class of the popup content div.
33449      */
33450     contentDisplayClass: "olFramedCloudPopupContent",
33451
33452     /**
33453      * APIProperty: autoSize
33454      * {Boolean} Framed Cloud is autosizing by default.
33455      */
33456     autoSize: true,
33457
33458     /**
33459      * APIProperty: panMapIfOutOfView
33460      * {Boolean} Framed Cloud does pan into view by default.
33461      */
33462     panMapIfOutOfView: true,
33463
33464     /**
33465      * APIProperty: imageSize
33466      * {<OpenLayers.Size>}
33467      */
33468     imageSize: new OpenLayers.Size(1276, 736),
33469
33470     /**
33471      * APIProperty: isAlphaImage
33472      * {Boolean} The FramedCloud does not use an alpha image (in honor of the 
33473      *     good ie6 folk out there)
33474      */
33475     isAlphaImage: false,
33476
33477     /** 
33478      * APIProperty: fixedRelativePosition
33479      * {Boolean} The Framed Cloud popup works in just one fixed position.
33480      */
33481     fixedRelativePosition: false,
33482
33483     /**
33484      * Property: positionBlocks
33485      * {Object} Hash of differen position blocks, keyed by relativePosition
33486      *     two-character code string (ie "tl", "tr", "bl", "br")
33487      */
33488     positionBlocks: {
33489         "tl": {
33490             'offset': new OpenLayers.Pixel(44, 0),
33491             'padding': new OpenLayers.Bounds(8, 40, 8, 9),
33492             'blocks': [
33493                 { // top-left
33494                     size: new OpenLayers.Size('auto', 'auto'),
33495                     anchor: new OpenLayers.Bounds(0, 51, 22, 0),
33496                     position: new OpenLayers.Pixel(0, 0)
33497                 },
33498                 { //top-right
33499                     size: new OpenLayers.Size(22, 'auto'),
33500                     anchor: new OpenLayers.Bounds(null, 50, 0, 0),
33501                     position: new OpenLayers.Pixel(-1238, 0)
33502                 },
33503                 { //bottom-left
33504                     size: new OpenLayers.Size('auto', 19),
33505                     anchor: new OpenLayers.Bounds(0, 32, 22, null),
33506                     position: new OpenLayers.Pixel(0, -631)
33507                 },
33508                 { //bottom-right
33509                     size: new OpenLayers.Size(22, 18),
33510                     anchor: new OpenLayers.Bounds(null, 32, 0, null),
33511                     position: new OpenLayers.Pixel(-1238, -632)
33512                 },
33513                 { // stem
33514                     size: new OpenLayers.Size(81, 35),
33515                     anchor: new OpenLayers.Bounds(null, 0, 0, null),
33516                     position: new OpenLayers.Pixel(0, -688)
33517                 }
33518             ]
33519         },
33520         "tr": {
33521             'offset': new OpenLayers.Pixel(-45, 0),
33522             'padding': new OpenLayers.Bounds(8, 40, 8, 9),
33523             'blocks': [
33524                 { // top-left
33525                     size: new OpenLayers.Size('auto', 'auto'),
33526                     anchor: new OpenLayers.Bounds(0, 51, 22, 0),
33527                     position: new OpenLayers.Pixel(0, 0)
33528                 },
33529                 { //top-right
33530                     size: new OpenLayers.Size(22, 'auto'),
33531                     anchor: new OpenLayers.Bounds(null, 50, 0, 0),
33532                     position: new OpenLayers.Pixel(-1238, 0)
33533                 },
33534                 { //bottom-left
33535                     size: new OpenLayers.Size('auto', 19),
33536                     anchor: new OpenLayers.Bounds(0, 32, 22, null),
33537                     position: new OpenLayers.Pixel(0, -631)
33538                 },
33539                 { //bottom-right
33540                     size: new OpenLayers.Size(22, 19),
33541                     anchor: new OpenLayers.Bounds(null, 32, 0, null),
33542                     position: new OpenLayers.Pixel(-1238, -631)
33543                 },
33544                 { // stem
33545                     size: new OpenLayers.Size(81, 35),
33546                     anchor: new OpenLayers.Bounds(0, 0, null, null),
33547                     position: new OpenLayers.Pixel(-215, -687)
33548                 }
33549             ]
33550         },
33551         "bl": {
33552             'offset': new OpenLayers.Pixel(45, 0),
33553             'padding': new OpenLayers.Bounds(8, 9, 8, 40),
33554             'blocks': [
33555                 { // top-left
33556                     size: new OpenLayers.Size('auto', 'auto'),
33557                     anchor: new OpenLayers.Bounds(0, 21, 22, 32),
33558                     position: new OpenLayers.Pixel(0, 0)
33559                 },
33560                 { //top-right
33561                     size: new OpenLayers.Size(22, 'auto'),
33562                     anchor: new OpenLayers.Bounds(null, 21, 0, 32),
33563                     position: new OpenLayers.Pixel(-1238, 0)
33564                 },
33565                 { //bottom-left
33566                     size: new OpenLayers.Size('auto', 21),
33567                     anchor: new OpenLayers.Bounds(0, 0, 22, null),
33568                     position: new OpenLayers.Pixel(0, -629)
33569                 },
33570                 { //bottom-right
33571                     size: new OpenLayers.Size(22, 21),
33572                     anchor: new OpenLayers.Bounds(null, 0, 0, null),
33573                     position: new OpenLayers.Pixel(-1238, -629)
33574                 },
33575                 { // stem
33576                     size: new OpenLayers.Size(81, 33),
33577                     anchor: new OpenLayers.Bounds(null, null, 0, 0),
33578                     position: new OpenLayers.Pixel(-101, -674)
33579                 }
33580             ]
33581         },
33582         "br": {
33583             'offset': new OpenLayers.Pixel(-44, 0),
33584             'padding': new OpenLayers.Bounds(8, 9, 8, 40),
33585             'blocks': [
33586                 { // top-left
33587                     size: new OpenLayers.Size('auto', 'auto'),
33588                     anchor: new OpenLayers.Bounds(0, 21, 22, 32),
33589                     position: new OpenLayers.Pixel(0, 0)
33590                 },
33591                 { //top-right
33592                     size: new OpenLayers.Size(22, 'auto'),
33593                     anchor: new OpenLayers.Bounds(null, 21, 0, 32),
33594                     position: new OpenLayers.Pixel(-1238, 0)
33595                 },
33596                 { //bottom-left
33597                     size: new OpenLayers.Size('auto', 21),
33598                     anchor: new OpenLayers.Bounds(0, 0, 22, null),
33599                     position: new OpenLayers.Pixel(0, -629)
33600                 },
33601                 { //bottom-right
33602                     size: new OpenLayers.Size(22, 21),
33603                     anchor: new OpenLayers.Bounds(null, 0, 0, null),
33604                     position: new OpenLayers.Pixel(-1238, -629)
33605                 },
33606                 { // stem
33607                     size: new OpenLayers.Size(81, 33),
33608                     anchor: new OpenLayers.Bounds(0, null, null, 0),
33609                     position: new OpenLayers.Pixel(-311, -674)
33610                 }
33611             ]
33612         }
33613     },
33614
33615     /**
33616      * APIProperty: minSize
33617      * {<OpenLayers.Size>}
33618      */
33619     minSize: new OpenLayers.Size(105, 10),
33620
33621     /**
33622      * APIProperty: maxSize
33623      * {<OpenLayers.Size>}
33624      */
33625     maxSize: new OpenLayers.Size(1200, 660),
33626
33627     /** 
33628      * Constructor: OpenLayers.Popup.FramedCloud
33629      * 
33630      * Parameters:
33631      * id - {String}
33632      * lonlat - {<OpenLayers.LonLat>}
33633      * contentSize - {<OpenLayers.Size>}
33634      * contentHTML - {String}
33635      * anchor - {Object} Object to which we'll anchor the popup. Must expose 
33636      *     a 'size' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>) 
33637      *     (Note that this is generally an <OpenLayers.Icon>).
33638      * closeBox - {Boolean}
33639      * closeBoxCallback - {Function} Function to be called on closeBox click.
33640      */
33641     initialize:function(id, lonlat, contentSize, contentHTML, anchor, closeBox, 
33642                         closeBoxCallback) {
33643
33644         this.imageSrc = OpenLayers.Util.getImageLocation('cloud-popup-relative.png');
33645         OpenLayers.Popup.Framed.prototype.initialize.apply(this, arguments);
33646         this.contentDiv.className = this.contentDisplayClass;
33647     },
33648
33649     CLASS_NAME: "OpenLayers.Popup.FramedCloud"
33650 });
33651 /* ======================================================================
33652     OpenLayers/Renderer/Canvas.js
33653    ====================================================================== */
33654
33655 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
33656  * full list of contributors). Published under the 2-clause BSD license.
33657  * See license.txt in the OpenLayers distribution or repository for the
33658  * full text of the license. */
33659
33660 /**
33661  * @requires OpenLayers/Renderer.js
33662  */
33663
33664 /**
33665  * Class: OpenLayers.Renderer.Canvas 
33666  * A renderer based on the 2D 'canvas' drawing element.
33667  * 
33668  * Inherits:
33669  *  - <OpenLayers.Renderer>
33670  */
33671 OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
33672     
33673     /**
33674      * APIProperty: hitDetection
33675      * {Boolean} Allow for hit detection of features.  Default is true.
33676      */
33677     hitDetection: true,
33678     
33679     /**
33680      * Property: hitOverflow
33681      * {Number} The method for converting feature identifiers to color values
33682      *     supports 16777215 sequential values.  Two features cannot be 
33683      *     predictably detected if their identifiers differ by more than this
33684      *     value.  The hitOverflow allows for bigger numbers (but the 
33685      *     difference in values is still limited).
33686      */
33687     hitOverflow: 0,
33688
33689     /**
33690      * Property: canvas
33691      * {Canvas} The canvas context object.
33692      */
33693     canvas: null, 
33694     
33695     /**
33696      * Property: features
33697      * {Object} Internal object of feature/style pairs for use in redrawing the layer.
33698      */
33699     features: null,
33700     
33701     /**
33702      * Property: pendingRedraw
33703      * {Boolean} The renderer needs a redraw call to render features added while
33704      *     the renderer was locked.
33705      */
33706     pendingRedraw: false,
33707     
33708     /**
33709      * Property: cachedSymbolBounds
33710      * {Object} Internal cache of calculated symbol extents.
33711      */
33712     cachedSymbolBounds: {},
33713     
33714     /**
33715      * Constructor: OpenLayers.Renderer.Canvas
33716      *
33717      * Parameters:
33718      * containerID - {<String>}
33719      * options - {Object} Optional properties to be set on the renderer.
33720      */
33721     initialize: function(containerID, options) {
33722         OpenLayers.Renderer.prototype.initialize.apply(this, arguments);
33723         this.root = document.createElement("canvas");
33724         this.container.appendChild(this.root);
33725         this.canvas = this.root.getContext("2d");
33726         this._clearRectId = OpenLayers.Util.createUniqueID();
33727         this.features = {};
33728         if (this.hitDetection) {
33729             this.hitCanvas = document.createElement("canvas");
33730             this.hitContext = this.hitCanvas.getContext("2d");
33731         }
33732     },
33733     
33734     /**
33735      * Method: setExtent
33736      * Set the visible part of the layer.
33737      *
33738      * Parameters:
33739      * extent - {<OpenLayers.Bounds>}
33740      * resolutionChanged - {Boolean}
33741      *
33742      * Returns:
33743      * {Boolean} true to notify the layer that the new extent does not exceed
33744      *     the coordinate range, and the features will not need to be redrawn.
33745      *     False otherwise.
33746      */
33747     setExtent: function() {
33748         OpenLayers.Renderer.prototype.setExtent.apply(this, arguments);
33749         // always redraw features
33750         return false;
33751     },
33752     
33753     /** 
33754      * Method: eraseGeometry
33755      * Erase a geometry from the renderer. Because the Canvas renderer has
33756      *     'memory' of the features that it has drawn, we have to remove the
33757      *     feature so it doesn't redraw.   
33758      * 
33759      * Parameters:
33760      * geometry - {<OpenLayers.Geometry>}
33761      * featureId - {String}
33762      */
33763     eraseGeometry: function(geometry, featureId) {
33764         this.eraseFeatures(this.features[featureId][0]);
33765     },
33766
33767     /**
33768      * APIMethod: supported
33769      * 
33770      * Returns:
33771      * {Boolean} Whether or not the browser supports the renderer class
33772      */
33773     supported: function() {
33774         return OpenLayers.CANVAS_SUPPORTED;
33775     },    
33776     
33777     /**
33778      * Method: setSize
33779      * Sets the size of the drawing surface.
33780      *
33781      * Once the size is updated, redraw the canvas.
33782      *
33783      * Parameters:
33784      * size - {<OpenLayers.Size>} 
33785      */
33786     setSize: function(size) {
33787         this.size = size.clone();
33788         var root = this.root;
33789         root.style.width = size.w + "px";
33790         root.style.height = size.h + "px";
33791         root.width = size.w;
33792         root.height = size.h;
33793         this.resolution = null;
33794         if (this.hitDetection) {
33795             var hitCanvas = this.hitCanvas;
33796             hitCanvas.style.width = size.w + "px";
33797             hitCanvas.style.height = size.h + "px";
33798             hitCanvas.width = size.w;
33799             hitCanvas.height = size.h;
33800         }
33801     },
33802     
33803     /**
33804      * Method: drawFeature
33805      * Draw the feature. Stores the feature in the features list,
33806      * then redraws the layer. 
33807      *
33808      * Parameters:
33809      * feature - {<OpenLayers.Feature.Vector>} 
33810      * style - {<Object>} 
33811      *
33812      * Returns:
33813      * {Boolean} The feature has been drawn completely.  If the feature has no
33814      *     geometry, undefined will be returned.  If the feature is not rendered
33815      *     for other reasons, false will be returned.
33816      */
33817     drawFeature: function(feature, style) {
33818         var rendered;
33819         if (feature.geometry) {
33820             style = this.applyDefaultSymbolizer(style || feature.style);
33821             // don't render if display none or feature outside extent
33822             var bounds = feature.geometry.getBounds();
33823
33824             var worldBounds;
33825             if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
33826                 worldBounds = this.map.getMaxExtent();
33827             }
33828
33829             var intersects = bounds && bounds.intersectsBounds(this.extent, {worldBounds: worldBounds});
33830
33831             rendered = (style.display !== "none") && !!bounds && intersects;
33832             if (rendered) {
33833                 // keep track of what we have rendered for redraw
33834                 this.features[feature.id] = [feature, style];
33835             }
33836             else {
33837                 // remove from features tracked for redraw
33838                 delete(this.features[feature.id]);
33839             }
33840             this.pendingRedraw = true;
33841         }
33842         if (this.pendingRedraw && !this.locked) {
33843             this.redraw();
33844             this.pendingRedraw = false;
33845         }
33846         return rendered;
33847     },
33848
33849     /** 
33850      * Method: drawGeometry
33851      * Used when looping (in redraw) over the features; draws
33852      * the canvas. 
33853      *
33854      * Parameters:
33855      * geometry - {<OpenLayers.Geometry>} 
33856      * style - {Object} 
33857      */
33858     drawGeometry: function(geometry, style, featureId) {
33859         var className = geometry.CLASS_NAME;
33860         if ((className == "OpenLayers.Geometry.Collection") ||
33861             (className == "OpenLayers.Geometry.MultiPoint") ||
33862             (className == "OpenLayers.Geometry.MultiLineString") ||
33863             (className == "OpenLayers.Geometry.MultiPolygon")) {
33864             var worldBounds = (this.map.baseLayer && this.map.baseLayer.wrapDateLine) && this.map.getMaxExtent();
33865             for (var i = 0; i < geometry.components.length; i++) {
33866                 this.calculateFeatureDx(geometry.components[i].getBounds(), worldBounds);
33867                 this.drawGeometry(geometry.components[i], style, featureId);
33868             }
33869             return;
33870         }
33871         switch (geometry.CLASS_NAME) {
33872             case "OpenLayers.Geometry.Point":
33873                 this.drawPoint(geometry, style, featureId);
33874                 break;
33875             case "OpenLayers.Geometry.LineString":
33876                 this.drawLineString(geometry, style, featureId);
33877                 break;
33878             case "OpenLayers.Geometry.LinearRing":
33879                 this.drawLinearRing(geometry, style, featureId);
33880                 break;
33881             case "OpenLayers.Geometry.Polygon":
33882                 this.drawPolygon(geometry, style, featureId);
33883                 break;
33884             default:
33885                 break;
33886         }
33887     },
33888
33889     /**
33890      * Method: drawExternalGraphic
33891      * Called to draw External graphics. 
33892      * 
33893      * Parameters: 
33894      * geometry - {<OpenLayers.Geometry>}
33895      * style    - {Object}
33896      * featureId - {String}
33897      */ 
33898     drawExternalGraphic: function(geometry, style, featureId) {
33899         var img = new Image();
33900
33901         var title = style.title || style.graphicTitle;        
33902         if (title) {
33903             img.title = title;           
33904         }
33905
33906         var width = style.graphicWidth || style.graphicHeight;
33907         var height = style.graphicHeight || style.graphicWidth;
33908         width = width ? width : style.pointRadius * 2;
33909         height = height ? height : style.pointRadius * 2;
33910         var xOffset = (style.graphicXOffset != undefined) ?
33911            style.graphicXOffset : -(0.5 * width);
33912         var yOffset = (style.graphicYOffset != undefined) ?
33913            style.graphicYOffset : -(0.5 * height);
33914         var _clearRectId = this._clearRectId;
33915
33916         var opacity = style.graphicOpacity || style.fillOpacity;
33917         
33918         var onLoad = function() {
33919             if(!this.features[featureId] ||
33920                                      _clearRectId !== this._clearRectId) {
33921                 return;
33922             }
33923             var pt = this.getLocalXY(geometry);
33924             var p0 = pt[0];
33925             var p1 = pt[1];
33926             if(!isNaN(p0) && !isNaN(p1)) {
33927                 var x = (p0 + xOffset) | 0;
33928                 var y = (p1 + yOffset) | 0;
33929                 var canvas = this.canvas;
33930                 canvas.globalAlpha = opacity;
33931                 var factor = OpenLayers.Renderer.Canvas.drawImageScaleFactor ||
33932                     (OpenLayers.Renderer.Canvas.drawImageScaleFactor =
33933                         /android 2.1/.test(navigator.userAgent.toLowerCase()) ?
33934                             // 320 is the screen width of the G1 phone, for
33935                             // which drawImage works out of the box.
33936                             320 / window.screen.width : 1
33937                     );
33938                 canvas.drawImage(
33939                     img, x*factor, y*factor, width*factor, height*factor
33940                 );
33941                 if (this.hitDetection) {
33942                     this.setHitContextStyle("fill", featureId);
33943                     this.hitContext.fillRect(x, y, width, height);
33944                 }
33945             }
33946         };
33947         img.onload = OpenLayers.Function.bind(onLoad, this);
33948         img.src = style.externalGraphic;
33949         if (img.complete) {
33950             img.onload();
33951             img.onload = null;
33952         }
33953     },
33954
33955     /**
33956      * Method: drawNamedSymbol
33957      * Called to draw Well Known Graphic Symbol Name. 
33958      * This method is only called by the renderer itself.
33959      * 
33960      * Parameters: 
33961      * geometry - {<OpenLayers.Geometry>}
33962      * style    - {Object}
33963      * featureId - {String}
33964      */ 
33965     drawNamedSymbol: function(geometry, style, featureId) {
33966         var x, y, cx, cy, i, symbolBounds, scaling, angle;
33967         var unscaledStrokeWidth;
33968         var deg2rad = Math.PI / 180.0;
33969         
33970         var symbol = OpenLayers.Renderer.symbol[style.graphicName];
33971          
33972         if (!symbol) {
33973             throw new Error(style.graphicName + ' is not a valid symbol name');
33974         }
33975         
33976         if (!symbol.length || symbol.length < 2) return;
33977         
33978         var pt = this.getLocalXY(geometry);
33979         var p0 = pt[0];
33980         var p1 = pt[1];
33981        
33982         if (isNaN(p0) || isNaN(p1)) return;
33983         
33984         // Use rounded line caps
33985         this.canvas.lineCap = "round";
33986         this.canvas.lineJoin = "round";
33987         
33988         if (this.hitDetection) {
33989             this.hitContext.lineCap = "round";
33990             this.hitContext.lineJoin = "round";
33991         }
33992         
33993         // Scale and rotate symbols, using precalculated bounds whenever possible.
33994         if (style.graphicName in this.cachedSymbolBounds) {
33995             symbolBounds = this.cachedSymbolBounds[style.graphicName];
33996         } else {
33997             symbolBounds = new OpenLayers.Bounds();
33998             for(i = 0; i < symbol.length; i+=2) {
33999                 symbolBounds.extend(new OpenLayers.LonLat(symbol[i], symbol[i+1]));
34000             }
34001             this.cachedSymbolBounds[style.graphicName] = symbolBounds;
34002         }
34003         
34004         // Push symbol scaling, translation and rotation onto the transformation stack in reverse order.
34005         // Don't forget to apply all canvas transformations to the hitContext canvas as well(!)
34006         this.canvas.save();
34007         if (this.hitDetection) { this.hitContext.save(); }
34008         
34009         // Step 3: place symbol at the desired location
34010         this.canvas.translate(p0,p1);
34011         if (this.hitDetection) { this.hitContext.translate(p0,p1); }
34012         
34013         // Step 2a. rotate the symbol if necessary
34014         angle = deg2rad * style.rotation; // will be NaN when style.rotation is undefined.
34015         if (!isNaN(angle)) {
34016             this.canvas.rotate(angle);
34017             if (this.hitDetection) { this.hitContext.rotate(angle); }
34018         }
34019                 
34020         // // Step 2: scale symbol such that pointRadius equals half the maximum symbol dimension.
34021         scaling = 2.0 * style.pointRadius / Math.max(symbolBounds.getWidth(), symbolBounds.getHeight());
34022         this.canvas.scale(scaling,scaling);
34023         if (this.hitDetection) { this.hitContext.scale(scaling,scaling); }
34024         
34025         // Step 1: center the symbol at the origin        
34026         cx = symbolBounds.getCenterLonLat().lon;
34027         cy = symbolBounds.getCenterLonLat().lat;
34028         this.canvas.translate(-cx,-cy);
34029         if (this.hitDetection) { this.hitContext.translate(-cx,-cy); }        
34030
34031         // Don't forget to scale stroke widths, because they are affected by canvas scale transformations as well(!)
34032         // Alternative: scale symbol coordinates manually, so stroke width scaling is not needed anymore.
34033         unscaledStrokeWidth = style.strokeWidth;
34034         style.strokeWidth = unscaledStrokeWidth / scaling;
34035             
34036         if (style.fill !== false) {
34037             this.setCanvasStyle("fill", style);
34038             this.canvas.beginPath();
34039             for (i=0; i<symbol.length; i=i+2) {
34040                 x = symbol[i];
34041                 y = symbol[i+1];
34042                 if (i == 0) this.canvas.moveTo(x,y);
34043                 this.canvas.lineTo(x,y);
34044             }
34045             this.canvas.closePath();
34046             this.canvas.fill();
34047
34048             if (this.hitDetection) {
34049                 this.setHitContextStyle("fill", featureId, style);
34050                 this.hitContext.beginPath();
34051                 for (i=0; i<symbol.length; i=i+2) {
34052                     x = symbol[i];
34053                     y = symbol[i+1];
34054                     if (i == 0) this.canvas.moveTo(x,y);
34055                     this.hitContext.lineTo(x,y);
34056                 }
34057                 this.hitContext.closePath();
34058                 this.hitContext.fill();
34059             }
34060         }  
34061         
34062         if (style.stroke !== false) {
34063             this.setCanvasStyle("stroke", style);
34064             this.canvas.beginPath();
34065             for (i=0; i<symbol.length; i=i+2) {
34066                 x = symbol[i];
34067                 y = symbol[i+1];
34068                 if (i == 0) this.canvas.moveTo(x,y);
34069                 this.canvas.lineTo(x,y);
34070             }
34071             this.canvas.closePath();
34072             this.canvas.stroke();
34073             
34074             
34075             if (this.hitDetection) {
34076                 this.setHitContextStyle("stroke", featureId, style, scaling);
34077                 this.hitContext.beginPath();
34078                 for (i=0; i<symbol.length; i=i+2) {
34079                     x = symbol[i];
34080                     y = symbol[i+1];
34081                     if (i == 0) this.hitContext.moveTo(x,y);
34082                     this.hitContext.lineTo(x,y);
34083                 }
34084                 this.hitContext.closePath();
34085                 this.hitContext.stroke();
34086             }
34087             
34088         }
34089         
34090         style.strokeWidth = unscaledStrokeWidth;
34091         this.canvas.restore();
34092         if (this.hitDetection) { this.hitContext.restore(); }
34093         this.setCanvasStyle("reset");  
34094     },
34095
34096     /**
34097      * Method: setCanvasStyle
34098      * Prepare the canvas for drawing by setting various global settings.
34099      *
34100      * Parameters:
34101      * type - {String} one of 'stroke', 'fill', or 'reset'
34102      * style - {Object} Symbolizer hash
34103      */
34104     setCanvasStyle: function(type, style) {
34105         if (type === "fill") {     
34106             this.canvas.globalAlpha = style['fillOpacity'];
34107             this.canvas.fillStyle = style['fillColor'];
34108         } else if (type === "stroke") {  
34109             this.canvas.globalAlpha = style['strokeOpacity'];
34110             this.canvas.strokeStyle = style['strokeColor'];
34111             this.canvas.lineWidth = style['strokeWidth'];
34112         } else {
34113             this.canvas.globalAlpha = 0;
34114             this.canvas.lineWidth = 1;
34115         }
34116     },
34117     
34118     /**
34119      * Method: featureIdToHex
34120      * Convert a feature ID string into an RGB hex string.
34121      *
34122      * Parameters:
34123      * featureId - {String} Feature id
34124      *
34125      * Returns:
34126      * {String} RGB hex string.
34127      */
34128     featureIdToHex: function(featureId) {
34129         var id = Number(featureId.split("_").pop()) + 1; // zero for no feature
34130         if (id >= 16777216) {
34131             this.hitOverflow = id - 16777215;
34132             id = id % 16777216 + 1;
34133         }
34134         var hex = "000000" + id.toString(16);
34135         var len = hex.length;
34136         hex = "#" + hex.substring(len-6, len);
34137         return hex;
34138     },
34139     
34140     /**
34141      * Method: setHitContextStyle
34142      * Prepare the hit canvas for drawing by setting various global settings.
34143      *
34144      * Parameters:
34145      * type - {String} one of 'stroke', 'fill', or 'reset'
34146      * featureId - {String} The feature id.
34147      * symbolizer - {<OpenLayers.Symbolizer>} The symbolizer.
34148      */
34149     setHitContextStyle: function(type, featureId, symbolizer, strokeScaling) {
34150         var hex = this.featureIdToHex(featureId);
34151         if (type == "fill") {
34152             this.hitContext.globalAlpha = 1.0;
34153             this.hitContext.fillStyle = hex;
34154         } else if (type == "stroke") {  
34155             this.hitContext.globalAlpha = 1.0;
34156             this.hitContext.strokeStyle = hex;
34157             // bump up stroke width to deal with antialiasing. If strokeScaling is defined, we're rendering a symbol 
34158             // on a transformed canvas, so the antialias width bump has to scale as well.
34159             if (typeof strokeScaling === "undefined") {
34160                 this.hitContext.lineWidth = symbolizer.strokeWidth + 2;
34161             } else {
34162                 if (!isNaN(strokeScaling)) { this.hitContext.lineWidth = symbolizer.strokeWidth + 2.0 / strokeScaling; }
34163             }
34164         } else {
34165             this.hitContext.globalAlpha = 0;
34166             this.hitContext.lineWidth = 1;
34167         }
34168     },
34169
34170     /**
34171      * Method: drawPoint
34172      * This method is only called by the renderer itself.
34173      * 
34174      * Parameters: 
34175      * geometry - {<OpenLayers.Geometry>}
34176      * style    - {Object}
34177      * featureId - {String}
34178      */ 
34179     drawPoint: function(geometry, style, featureId) {
34180         if(style.graphic !== false) {
34181             if(style.externalGraphic) {
34182                 this.drawExternalGraphic(geometry, style, featureId);
34183             } else if (style.graphicName && (style.graphicName != "circle")) {
34184                 this.drawNamedSymbol(geometry, style, featureId);
34185             } else {
34186                 var pt = this.getLocalXY(geometry);
34187                 var p0 = pt[0];
34188                 var p1 = pt[1];
34189                 if(!isNaN(p0) && !isNaN(p1)) {
34190                     var twoPi = Math.PI*2;
34191                     var radius = style.pointRadius;
34192                     if(style.fill !== false) {
34193                         this.setCanvasStyle("fill", style);
34194                         this.canvas.beginPath();
34195                         this.canvas.arc(p0, p1, radius, 0, twoPi, true);
34196                         this.canvas.fill();
34197                         if (this.hitDetection) {
34198                             this.setHitContextStyle("fill", featureId, style);
34199                             this.hitContext.beginPath();
34200                             this.hitContext.arc(p0, p1, radius, 0, twoPi, true);
34201                             this.hitContext.fill();
34202                         }
34203                     }
34204
34205                     if(style.stroke !== false) {
34206                         this.setCanvasStyle("stroke", style);
34207                         this.canvas.beginPath();
34208                         this.canvas.arc(p0, p1, radius, 0, twoPi, true);
34209                         this.canvas.stroke();
34210                         if (this.hitDetection) {
34211                             this.setHitContextStyle("stroke", featureId, style);
34212                             this.hitContext.beginPath();
34213                             this.hitContext.arc(p0, p1, radius, 0, twoPi, true);
34214                             this.hitContext.stroke();
34215                         }
34216                         this.setCanvasStyle("reset");
34217                     }
34218                 }
34219             }
34220         }
34221     },
34222     
34223     /**
34224      * Method: drawLineString
34225      * This method is only called by the renderer itself.
34226      * 
34227      * Parameters: 
34228      * geometry - {<OpenLayers.Geometry>}
34229      * style    - {Object}
34230      * featureId - {String}
34231      */ 
34232     drawLineString: function(geometry, style, featureId) {
34233         style = OpenLayers.Util.applyDefaults({fill: false}, style);
34234         this.drawLinearRing(geometry, style, featureId);
34235     },    
34236     
34237     /**
34238      * Method: drawLinearRing
34239      * This method is only called by the renderer itself.
34240      * 
34241      * Parameters: 
34242      * geometry - {<OpenLayers.Geometry>}
34243      * style    - {Object}
34244      * featureId - {String}
34245      */ 
34246     drawLinearRing: function(geometry, style, featureId) {
34247         if (style.fill !== false) {
34248             this.setCanvasStyle("fill", style);
34249             this.renderPath(this.canvas, geometry, style, featureId, "fill");
34250             if (this.hitDetection) {
34251                 this.setHitContextStyle("fill", featureId, style);
34252                 this.renderPath(this.hitContext, geometry, style, featureId, "fill");
34253             }
34254         }
34255         if (style.stroke !== false) {
34256             this.setCanvasStyle("stroke", style);
34257             this.renderPath(this.canvas, geometry, style, featureId, "stroke");
34258             if (this.hitDetection) {
34259                 this.setHitContextStyle("stroke", featureId, style);
34260                 this.renderPath(this.hitContext, geometry, style, featureId, "stroke");
34261             }
34262         }
34263         this.setCanvasStyle("reset");
34264     },
34265     
34266     /**
34267      * Method: renderPath
34268      * Render a path with stroke and optional fill.
34269      */
34270     renderPath: function(context, geometry, style, featureId, type) {
34271         var components = geometry.components;
34272         var len = components.length;
34273         context.beginPath();
34274         var start = this.getLocalXY(components[0]);
34275         var x = start[0];
34276         var y = start[1];
34277         if (!isNaN(x) && !isNaN(y)) {
34278             context.moveTo(start[0], start[1]);
34279             for (var i=1; i<len; ++i) {
34280                 var pt = this.getLocalXY(components[i]);
34281                 context.lineTo(pt[0], pt[1]);
34282             }
34283             if (type === "fill") {
34284                 context.fill();
34285             } else {
34286                 context.stroke();
34287             }
34288         }
34289     },
34290     
34291     /**
34292      * Method: drawPolygon
34293      * This method is only called by the renderer itself.
34294      * 
34295      * Parameters: 
34296      * geometry - {<OpenLayers.Geometry>}
34297      * style    - {Object}
34298      * featureId - {String}
34299      */ 
34300     drawPolygon: function(geometry, style, featureId) {
34301         var components = geometry.components;
34302         var len = components.length;
34303         this.drawLinearRing(components[0], style, featureId);
34304         // erase inner rings
34305         for (var i=1; i<len; ++i) {
34306             /** 
34307              * Note that this is overly aggressive.  Here we punch holes through 
34308              * all previously rendered features on the same canvas.  A better 
34309              * solution for polygons with interior rings would be to draw the 
34310              * polygon on a sketch canvas first.  We could erase all holes 
34311              * there and then copy the drawing to the layer canvas. 
34312              * TODO: http://trac.osgeo.org/openlayers/ticket/3130 
34313              */
34314             this.canvas.globalCompositeOperation = "destination-out";
34315             if (this.hitDetection) {
34316                 this.hitContext.globalCompositeOperation = "destination-out";
34317             }
34318             this.drawLinearRing(
34319                 components[i], 
34320                 OpenLayers.Util.applyDefaults({stroke: false, fillOpacity: 1.0}, style),
34321                 featureId
34322             );
34323             this.canvas.globalCompositeOperation = "source-over";
34324             if (this.hitDetection) {
34325                 this.hitContext.globalCompositeOperation = "source-over";
34326             }
34327             this.drawLinearRing(
34328                 components[i], 
34329                 OpenLayers.Util.applyDefaults({fill: false}, style),
34330                 featureId
34331             );
34332         }
34333     },
34334     
34335     /**
34336      * Method: drawText
34337      * This method is only called by the renderer itself.
34338      *
34339      * Parameters:
34340      * location - {<OpenLayers.Point>}
34341      * style    - {Object}
34342      */
34343     drawText: function(location, style) {
34344         var pt = this.getLocalXY(location);
34345
34346         this.setCanvasStyle("reset");
34347         this.canvas.fillStyle = style.fontColor;
34348         this.canvas.globalAlpha = style.fontOpacity || 1.0;
34349         var fontStyle = [style.fontStyle ? style.fontStyle : "normal",
34350                          "normal", // "font-variant" not supported
34351                          style.fontWeight ? style.fontWeight : "normal",
34352                          style.fontSize ? style.fontSize : "1em",
34353                          style.fontFamily ? style.fontFamily : "sans-serif"].join(" ");
34354         var labelRows = style.label.split('\n');
34355         var numRows = labelRows.length;
34356         if (this.canvas.fillText) {
34357             // HTML5
34358             this.canvas.font = fontStyle;
34359             this.canvas.textAlign =
34360                 OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[0]] ||
34361                 "center";
34362             this.canvas.textBaseline =
34363                 OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[1]] ||
34364                 "middle";
34365             var vfactor =
34366                 OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[1]];
34367             if (vfactor == null) {
34368                 vfactor = -.5;
34369             }
34370             var lineHeight =
34371                 this.canvas.measureText('Mg').height ||
34372                 this.canvas.measureText('xx').width;
34373             pt[1] += lineHeight*vfactor*(numRows-1);
34374             for (var i = 0; i < numRows; i++) {
34375                 if (style.labelOutlineWidth) {
34376                     this.canvas.save();
34377                     this.canvas.globalAlpha = style.labelOutlineOpacity || style.fontOpacity || 1.0;
34378                     this.canvas.strokeStyle = style.labelOutlineColor;
34379                     this.canvas.lineWidth = style.labelOutlineWidth;
34380                     this.canvas.strokeText(labelRows[i], pt[0], pt[1] + (lineHeight*i) + 1);
34381                     this.canvas.restore();
34382                 }
34383                 this.canvas.fillText(labelRows[i], pt[0], pt[1] + (lineHeight*i));
34384             }
34385         } else if (this.canvas.mozDrawText) {
34386             // Mozilla pre-Gecko1.9.1 (<FF3.1)
34387             this.canvas.mozTextStyle = fontStyle;
34388             // No built-in text alignment, so we measure and adjust the position
34389             var hfactor =
34390                 OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[0]];
34391             if (hfactor == null) {
34392                 hfactor = -.5;
34393             }
34394             var vfactor =
34395                 OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[1]];
34396             if (vfactor == null) {
34397                 vfactor = -.5;
34398             }
34399             var lineHeight = this.canvas.mozMeasureText('xx');
34400             pt[1] += lineHeight*(1 + (vfactor*numRows));
34401             for (var i = 0; i < numRows; i++) {
34402                 var x = pt[0] + (hfactor*this.canvas.mozMeasureText(labelRows[i]));
34403                 var y = pt[1] + (i*lineHeight);
34404                 this.canvas.translate(x, y);
34405                 this.canvas.mozDrawText(labelRows[i]);
34406                 this.canvas.translate(-x, -y);
34407             }
34408         }
34409         this.setCanvasStyle("reset");
34410     },
34411     
34412     /**
34413      * Method: getLocalXY
34414      * transform geographic xy into pixel xy
34415      *
34416      * Parameters: 
34417      * point - {<OpenLayers.Geometry.Point>}
34418      */
34419     getLocalXY: function(point) {
34420         var resolution = this.getResolution();
34421         var extent = this.extent;
34422         var x = ((point.x - this.featureDx) / resolution + (-extent.left / resolution));
34423         var y = ((extent.top / resolution) - point.y / resolution);
34424         return [x, y];
34425     },
34426
34427     /**
34428      * Method: clear
34429      * Clear all vectors from the renderer.
34430      */    
34431     clear: function() {
34432         this.clearCanvas();
34433         this.features = {};
34434     },
34435
34436     /**
34437      * Method: clearCanvas
34438      * Clear the canvas element of the renderer.
34439      */    
34440     clearCanvas: function() {
34441         var height = this.root.height;
34442         var width = this.root.width;
34443         this.canvas.clearRect(0, 0, width, height);
34444         this._clearRectId = OpenLayers.Util.createUniqueID();
34445         if (this.hitDetection) {
34446             this.hitContext.clearRect(0, 0, width, height);
34447         }
34448     },
34449
34450     /**
34451      * Method: getFeatureIdFromEvent
34452      * Returns a feature id from an event on the renderer.  
34453      * 
34454      * Parameters:
34455      * evt - {<OpenLayers.Event>} 
34456      *
34457      * Returns:
34458      * {<OpenLayers.Feature.Vector} A feature or undefined.  This method returns a 
34459      *     feature instead of a feature id to avoid an unnecessary lookup on the
34460      *     layer.
34461      */
34462     getFeatureIdFromEvent: function(evt) {
34463         var featureId, feature;
34464         
34465         if (this.hitDetection && this.root.style.display !== "none") {
34466             // this dragging check should go in the feature handler
34467             if (!this.map.dragging) {
34468                 var xy = evt.xy;
34469                 var x = xy.x | 0;
34470                 var y = xy.y | 0;
34471                 var data = this.hitContext.getImageData(x, y, 1, 1).data;
34472                 if (data[3] === 255) { // antialiased
34473                     var id = data[2] + (256 * (data[1] + (256 * data[0])));
34474                     if (id) {
34475                         featureId = "OpenLayers_Feature_Vector_" + (id - 1 + this.hitOverflow);
34476                         try {
34477                             feature = this.features[featureId][0];
34478                         } catch(err) {
34479                             // Because of antialiasing on the canvas, when the hit location is at a point where the edge of
34480                             // one symbol intersects the interior of another symbol, a wrong hit color (and therefore id) results.
34481                             // todo: set Antialiasing = 'off' on the hitContext as soon as browsers allow it.
34482                         }
34483                     }
34484                 }
34485             }
34486         }
34487         return feature;
34488     },
34489     
34490     /**
34491      * Method: eraseFeatures 
34492      * This is called by the layer to erase features; removes the feature from
34493      *     the list, then redraws the layer.
34494      * 
34495      * Parameters:
34496      * features - {Array(<OpenLayers.Feature.Vector>)} 
34497      */
34498     eraseFeatures: function(features) {
34499         if(!(OpenLayers.Util.isArray(features))) {
34500             features = [features];
34501         }
34502         for(var i=0; i<features.length; ++i) {
34503             delete this.features[features[i].id];
34504         }
34505         this.redraw();
34506     },
34507
34508     /**
34509      * Method: redraw
34510      * The real 'meat' of the function: any time things have changed,
34511      *     redraw() can be called to loop over all the data and (you guessed
34512      *     it) redraw it.  Unlike Elements-based Renderers, we can't interact
34513      *     with things once they're drawn, to remove them, for example, so
34514      *     instead we have to just clear everything and draw from scratch.
34515      */
34516     redraw: function() {
34517         if (!this.locked) {
34518             this.clearCanvas();
34519             var labelMap = [];
34520             var feature, geometry, style;
34521             var worldBounds = (this.map.baseLayer && this.map.baseLayer.wrapDateLine) && this.map.getMaxExtent();
34522             for (var id in this.features) {
34523                 if (!this.features.hasOwnProperty(id)) { continue; }
34524                 feature = this.features[id][0];
34525                 geometry = feature.geometry;
34526                 this.calculateFeatureDx(geometry.getBounds(), worldBounds);
34527                 style = this.features[id][1];
34528                 this.drawGeometry(geometry, style, feature.id);
34529                 if(style.label) {
34530                     labelMap.push([feature, style]);
34531                 }
34532             }
34533             var item;
34534             for (var i=0, len=labelMap.length; i<len; ++i) {
34535                 item = labelMap[i];
34536                 this.drawText(item[0].geometry.getCentroid(), item[1]);
34537             }
34538         }    
34539     },
34540
34541     CLASS_NAME: "OpenLayers.Renderer.Canvas"
34542 });
34543
34544 /**
34545  * Constant: OpenLayers.Renderer.Canvas.LABEL_ALIGN
34546  * {Object}
34547  */
34548 OpenLayers.Renderer.Canvas.LABEL_ALIGN = {
34549     "l": "left",
34550     "r": "right",
34551     "t": "top",
34552     "b": "bottom"
34553 };
34554
34555 /**
34556  * Constant: OpenLayers.Renderer.Canvas.LABEL_FACTOR
34557  * {Object}
34558  */
34559 OpenLayers.Renderer.Canvas.LABEL_FACTOR = {
34560     "l": 0,
34561     "r": -1,
34562     "t": 0,
34563     "b": -1
34564 };
34565
34566 /**
34567  * Constant: OpenLayers.Renderer.Canvas.drawImageScaleFactor
34568  * {Number} Scale factor to apply to the canvas drawImage arguments. This
34569  *     is always 1 except for Android 2.1 devices, to work around
34570  *     http://code.google.com/p/android/issues/detail?id=5141.
34571  */
34572 OpenLayers.Renderer.Canvas.drawImageScaleFactor = null;
34573 /* ======================================================================
34574     OpenLayers/Renderer/Elements.js
34575    ====================================================================== */
34576
34577 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
34578  * full list of contributors). Published under the 2-clause BSD license.
34579  * See license.txt in the OpenLayers distribution or repository for the
34580  * full text of the license. */
34581
34582 /**
34583  * @requires OpenLayers/Renderer.js
34584  */
34585
34586 /**
34587  * Class: OpenLayers.ElementsIndexer
34588  * This class takes care of figuring out which order elements should be
34589  *     placed in the DOM based on given indexing methods. 
34590  */
34591 OpenLayers.ElementsIndexer = OpenLayers.Class({
34592    
34593     /**
34594      * Property: maxZIndex
34595      * {Integer} This is the largest-most z-index value for a node
34596      *     contained within the indexer.
34597      */
34598     maxZIndex: null,
34599     
34600     /**
34601      * Property: order
34602      * {Array<String>} This is an array of node id's stored in the
34603      *     order that they should show up on screen. Id's higher up in the
34604      *     array (higher array index) represent nodes with higher z-indeces.
34605      */
34606     order: null, 
34607     
34608     /**
34609      * Property: indices
34610      * {Object} This is a hash that maps node ids to their z-index value
34611      *     stored in the indexer. This is done to make finding a nodes z-index 
34612      *     value O(1).
34613      */
34614     indices: null,
34615     
34616     /**
34617      * Property: compare
34618      * {Function} This is the function used to determine placement of
34619      *     of a new node within the indexer. If null, this defaults to to
34620      *     the Z_ORDER_DRAWING_ORDER comparison method.
34621      */
34622     compare: null,
34623     
34624     /**
34625      * APIMethod: initialize
34626      * Create a new indexer with 
34627      * 
34628      * Parameters:
34629      * yOrdering - {Boolean} Whether to use y-ordering.
34630      */
34631     initialize: function(yOrdering) {
34632
34633         this.compare = yOrdering ? 
34634             OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_Y_ORDER :
34635             OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_DRAWING_ORDER;
34636
34637         this.clear();
34638     },
34639     
34640     /**
34641      * APIMethod: insert
34642      * Insert a new node into the indexer. In order to find the correct 
34643      *     positioning for the node to be inserted, this method uses a binary 
34644      *     search. This makes inserting O(log(n)). 
34645      * 
34646      * Parameters:
34647      * newNode - {DOMElement} The new node to be inserted.
34648      * 
34649      * Returns
34650      * {DOMElement} the node before which we should insert our newNode, or
34651      *     null if newNode can just be appended.
34652      */
34653     insert: function(newNode) {
34654         // If the node is known to the indexer, remove it so we can
34655         // recalculate where it should go.
34656         if (this.exists(newNode)) {
34657             this.remove(newNode);
34658         }
34659         
34660         var nodeId = newNode.id;
34661         
34662         this.determineZIndex(newNode);       
34663
34664         var leftIndex = -1;
34665         var rightIndex = this.order.length;
34666         var middle;
34667
34668         while (rightIndex - leftIndex > 1) {
34669             middle = parseInt((leftIndex + rightIndex) / 2);
34670             
34671             var placement = this.compare(this, newNode,
34672                 OpenLayers.Util.getElement(this.order[middle]));
34673             
34674             if (placement > 0) {
34675                 leftIndex = middle;
34676             } else {
34677                 rightIndex = middle;
34678             } 
34679         }
34680         
34681         this.order.splice(rightIndex, 0, nodeId);
34682         this.indices[nodeId] = this.getZIndex(newNode);
34683         
34684         // If the new node should be before another in the index
34685         // order, return the node before which we have to insert the new one;
34686         // else, return null to indicate that the new node can be appended.
34687         return this.getNextElement(rightIndex);
34688     },
34689     
34690     /**
34691      * APIMethod: remove
34692      * 
34693      * Parameters:
34694      * node - {DOMElement} The node to be removed.
34695      */
34696     remove: function(node) {
34697         var nodeId = node.id;
34698         var arrayIndex = OpenLayers.Util.indexOf(this.order, nodeId);
34699         if (arrayIndex >= 0) {
34700             // Remove it from the order array, as well as deleting the node
34701             // from the indeces hash.
34702             this.order.splice(arrayIndex, 1);
34703             delete this.indices[nodeId];
34704             
34705             // Reset the maxium z-index based on the last item in the 
34706             // order array.
34707             if (this.order.length > 0) {
34708                 var lastId = this.order[this.order.length - 1];
34709                 this.maxZIndex = this.indices[lastId];
34710             } else {
34711                 this.maxZIndex = 0;
34712             }
34713         }
34714     },
34715     
34716     /**
34717      * APIMethod: clear
34718      */
34719     clear: function() {
34720         this.order = [];
34721         this.indices = {};
34722         this.maxZIndex = 0;
34723     },
34724     
34725     /**
34726      * APIMethod: exists
34727      *
34728      * Parameters:
34729      * node - {DOMElement} The node to test for existence.
34730      *
34731      * Returns:
34732      * {Boolean} Whether or not the node exists in the indexer?
34733      */
34734     exists: function(node) {
34735         return (this.indices[node.id] != null);
34736     },
34737
34738     /**
34739      * APIMethod: getZIndex
34740      * Get the z-index value for the current node from the node data itself.
34741      * 
34742      * Parameters:
34743      * node - {DOMElement} The node whose z-index to get.
34744      * 
34745      * Returns:
34746      * {Integer} The z-index value for the specified node (from the node 
34747      *     data itself).
34748      */
34749     getZIndex: function(node) {
34750         return node._style.graphicZIndex;  
34751     },
34752     
34753     /**
34754      * Method: determineZIndex
34755      * Determine the z-index for the current node if there isn't one, 
34756      *     and set the maximum value if we've found a new maximum.
34757      * 
34758      * Parameters:
34759      * node - {DOMElement} 
34760      */
34761     determineZIndex: function(node) {
34762         var zIndex = node._style.graphicZIndex;
34763         
34764         // Everything must have a zIndex. If none is specified,
34765         // this means the user *must* (hint: assumption) want this
34766         // node to succomb to drawing order. To enforce drawing order
34767         // over all indexing methods, we'll create a new z-index that's
34768         // greater than any currently in the indexer.
34769         if (zIndex == null) {
34770             zIndex = this.maxZIndex;
34771             node._style.graphicZIndex = zIndex; 
34772         } else if (zIndex > this.maxZIndex) {
34773             this.maxZIndex = zIndex;
34774         }
34775     },
34776
34777     /**
34778      * APIMethod: getNextElement
34779      * Get the next element in the order stack.
34780      * 
34781      * Parameters:
34782      * index - {Integer} The index of the current node in this.order.
34783      * 
34784      * Returns:
34785      * {DOMElement} the node following the index passed in, or
34786      *     null.
34787      */
34788     getNextElement: function(index) {
34789         for (var nextIndex = index + 1, nextElement = undefined;
34790             (nextIndex < this.order.length) && (nextElement == undefined);
34791             nextIndex++) {
34792             nextElement = OpenLayers.Util.getElement(this.order[nextIndex]);
34793         }
34794         
34795         return nextElement || null;
34796     },
34797     
34798     CLASS_NAME: "OpenLayers.ElementsIndexer"
34799 });
34800
34801 /**
34802  * Namespace: OpenLayers.ElementsIndexer.IndexingMethods
34803  * These are the compare methods for figuring out where a new node should be 
34804  *     placed within the indexer. These methods are very similar to general 
34805  *     sorting methods in that they return -1, 0, and 1 to specify the 
34806  *     direction in which new nodes fall in the ordering.
34807  */
34808 OpenLayers.ElementsIndexer.IndexingMethods = {
34809     
34810     /**
34811      * Method: Z_ORDER
34812      * This compare method is used by other comparison methods.
34813      *     It can be used individually for ordering, but is not recommended,
34814      *     because it doesn't subscribe to drawing order.
34815      * 
34816      * Parameters:
34817      * indexer - {<OpenLayers.ElementsIndexer>}
34818      * newNode - {DOMElement}
34819      * nextNode - {DOMElement}
34820      * 
34821      * Returns:
34822      * {Integer}
34823      */
34824     Z_ORDER: function(indexer, newNode, nextNode) {
34825         var newZIndex = indexer.getZIndex(newNode);
34826
34827         var returnVal = 0;
34828         if (nextNode) {
34829             var nextZIndex = indexer.getZIndex(nextNode);
34830             returnVal = newZIndex - nextZIndex; 
34831         }
34832         
34833         return returnVal;
34834     },
34835
34836     /**
34837      * APIMethod: Z_ORDER_DRAWING_ORDER
34838      * This method orders nodes by their z-index, but does so in a way
34839      *     that, if there are other nodes with the same z-index, the newest 
34840      *     drawn will be the front most within that z-index. This is the 
34841      *     default indexing method.
34842      * 
34843      * Parameters:
34844      * indexer - {<OpenLayers.ElementsIndexer>}
34845      * newNode - {DOMElement}
34846      * nextNode - {DOMElement}
34847      * 
34848      * Returns:
34849      * {Integer}
34850      */
34851     Z_ORDER_DRAWING_ORDER: function(indexer, newNode, nextNode) {
34852         var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(
34853             indexer, 
34854             newNode, 
34855             nextNode
34856         );
34857         
34858         // Make Z_ORDER subscribe to drawing order by pushing it above
34859         // all of the other nodes with the same z-index.
34860         if (nextNode && returnVal == 0) {
34861             returnVal = 1;
34862         }
34863         
34864         return returnVal;
34865     },
34866
34867     /**
34868      * APIMethod: Z_ORDER_Y_ORDER
34869      * This one should really be called Z_ORDER_Y_ORDER_DRAWING_ORDER, as it
34870      *     best describes which ordering methods have precedence (though, the 
34871      *     name would be too long). This method orders nodes by their z-index, 
34872      *     but does so in a way that, if there are other nodes with the same 
34873      *     z-index, the nodes with the lower y position will be "closer" than 
34874      *     those with a higher y position. If two nodes have the exact same y 
34875      *     position, however, then this method will revert to using drawing  
34876      *     order to decide placement.
34877      * 
34878      * Parameters:
34879      * indexer - {<OpenLayers.ElementsIndexer>}
34880      * newNode - {DOMElement}
34881      * nextNode - {DOMElement}
34882      * 
34883      * Returns:
34884      * {Integer}
34885      */
34886     Z_ORDER_Y_ORDER: function(indexer, newNode, nextNode) {
34887         var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(
34888             indexer, 
34889             newNode, 
34890             nextNode
34891         );
34892         
34893         if (nextNode && returnVal === 0) {            
34894             var result = nextNode._boundsBottom - newNode._boundsBottom;
34895             returnVal = (result === 0) ? 1 : result;
34896         }
34897         
34898         return returnVal;       
34899     }
34900 };
34901
34902 /**
34903  * Class: OpenLayers.Renderer.Elements
34904  * This is another virtual class in that it should never be instantiated by 
34905  *  itself as a Renderer. It exists because there is *tons* of shared 
34906  *  functionality between different vector libraries which use nodes/elements
34907  *  as a base for rendering vectors. 
34908  * 
34909  * The highlevel bits of code that are implemented here are the adding and 
34910  *  removing of geometries, which is essentially the same for any 
34911  *  element-based renderer. The details of creating each node and drawing the
34912  *  paths are of course different, but the machinery is the same. 
34913  * 
34914  * Inherits:
34915  *  - <OpenLayers.Renderer>
34916  */
34917 OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
34918
34919     /**
34920      * Property: rendererRoot
34921      * {DOMElement}
34922      */
34923     rendererRoot: null,
34924     
34925     /**
34926      * Property: root
34927      * {DOMElement}
34928      */
34929     root: null,
34930     
34931     /**
34932      * Property: vectorRoot
34933      * {DOMElement}
34934      */
34935     vectorRoot: null,
34936
34937     /**
34938      * Property: textRoot
34939      * {DOMElement}
34940      */
34941     textRoot: null,
34942
34943     /**
34944      * Property: xmlns
34945      * {String}
34946      */    
34947     xmlns: null,
34948     
34949     /**
34950      * Property: xOffset
34951      * {Number} Offset to apply to the renderer viewport translation in x
34952      * direction. If the renderer extent's center is on the right of the
34953      * dateline (i.e. exceeds the world bounds), we shift the viewport to the
34954      * left by one world width. This avoids that features disappear from the
34955      * map viewport. Because our dateline handling logic in other places
34956      * ensures that extents crossing the dateline always have a center
34957      * exceeding the world bounds on the left, we need this offset to make sure
34958      * that the same is true for the renderer extent in pixel space as well.
34959      */
34960     xOffset: 0,
34961     
34962     /**
34963      * Property: rightOfDateLine
34964      * {Boolean} Keeps track of the location of the map extent relative to the
34965      * date line. The <setExtent> method compares this value (which is the one
34966      * from the previous <setExtent> call) with the current position of the map
34967      * extent relative to the date line and updates the xOffset when the extent
34968      * has moved from one side of the date line to the other.
34969      */
34970     
34971     /**
34972      * Property: Indexer
34973      * {<OpenLayers.ElementIndexer>} An instance of OpenLayers.ElementsIndexer 
34974      *     created upon initialization if the zIndexing or yOrdering options
34975      *     passed to this renderer's constructor are set to true.
34976      */
34977     indexer: null, 
34978     
34979     /**
34980      * Constant: BACKGROUND_ID_SUFFIX
34981      * {String}
34982      */
34983     BACKGROUND_ID_SUFFIX: "_background",
34984     
34985     /**
34986      * Constant: LABEL_ID_SUFFIX
34987      * {String}
34988      */
34989     LABEL_ID_SUFFIX: "_label",
34990     
34991     /**
34992      * Constant: LABEL_OUTLINE_SUFFIX
34993      * {String}
34994      */
34995     LABEL_OUTLINE_SUFFIX: "_outline",
34996
34997     /**
34998      * Constructor: OpenLayers.Renderer.Elements
34999      * 
35000      * Parameters:
35001      * containerID - {String}
35002      * options - {Object} options for this renderer. 
35003      *
35004      * Supported options are:
35005      *     yOrdering - {Boolean} Whether to use y-ordering
35006      *     zIndexing - {Boolean} Whether to use z-indexing. Will be ignored
35007      *         if yOrdering is set to true.
35008      */
35009     initialize: function(containerID, options) {
35010         OpenLayers.Renderer.prototype.initialize.apply(this, arguments);
35011
35012         this.rendererRoot = this.createRenderRoot();
35013         this.root = this.createRoot("_root");
35014         this.vectorRoot = this.createRoot("_vroot");
35015         this.textRoot = this.createRoot("_troot");
35016         
35017         this.root.appendChild(this.vectorRoot);
35018         this.root.appendChild(this.textRoot);
35019         
35020         this.rendererRoot.appendChild(this.root);
35021         this.container.appendChild(this.rendererRoot);
35022         
35023         if(options && (options.zIndexing || options.yOrdering)) {
35024             this.indexer = new OpenLayers.ElementsIndexer(options.yOrdering);
35025         }
35026     },
35027     
35028     /**
35029      * Method: destroy
35030      */
35031     destroy: function() {
35032
35033         this.clear(); 
35034
35035         this.rendererRoot = null;
35036         this.root = null;
35037         this.xmlns = null;
35038
35039         OpenLayers.Renderer.prototype.destroy.apply(this, arguments);
35040     },
35041     
35042     /**
35043      * Method: clear
35044      * Remove all the elements from the root
35045      */    
35046     clear: function() {
35047         var child;
35048         var root = this.vectorRoot;
35049         if (root) {
35050             while (child = root.firstChild) {
35051                 root.removeChild(child);
35052             }
35053         }
35054         root = this.textRoot;
35055         if (root) {
35056             while (child = root.firstChild) {
35057                 root.removeChild(child);
35058             }
35059         }
35060         if (this.indexer) {
35061             this.indexer.clear();
35062         }
35063     },
35064     
35065     /**
35066      * Method: setExtent
35067      * Set the visible part of the layer.
35068      *
35069      * Parameters:
35070      * extent - {<OpenLayers.Bounds>}
35071      * resolutionChanged - {Boolean}
35072      *
35073      * Returns:
35074      * {Boolean} true to notify the layer that the new extent does not exceed
35075      *     the coordinate range, and the features will not need to be redrawn.
35076      *     False otherwise.
35077      */
35078     setExtent: function(extent, resolutionChanged) {
35079         var coordSysUnchanged = OpenLayers.Renderer.prototype.setExtent.apply(this, arguments);
35080         var resolution = this.getResolution();
35081         if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
35082             var rightOfDateLine,
35083                 ratio = extent.getWidth() / this.map.getExtent().getWidth(),
35084                 extent = extent.scale(1 / ratio),
35085                 world = this.map.getMaxExtent();
35086             if (world.right > extent.left && world.right < extent.right) {
35087                 rightOfDateLine = true;
35088             } else if (world.left > extent.left && world.left < extent.right) {
35089                 rightOfDateLine = false;
35090             }
35091             if (rightOfDateLine !== this.rightOfDateLine || resolutionChanged) {
35092                 coordSysUnchanged = false;
35093                 this.xOffset = rightOfDateLine === true ?
35094                     world.getWidth() / resolution : 0;
35095             }
35096             this.rightOfDateLine = rightOfDateLine;
35097         }
35098         return coordSysUnchanged;
35099     },
35100
35101     /** 
35102      * Method: getNodeType
35103      * This function is in charge of asking the specific renderer which type
35104      *     of node to create for the given geometry and style. All geometries
35105      *     in an Elements-based renderer consist of one node and some
35106      *     attributes. We have the nodeFactory() function which creates a node
35107      *     for us, but it takes a 'type' as input, and that is precisely what
35108      *     this function tells us.  
35109      *  
35110      * Parameters:
35111      * geometry - {<OpenLayers.Geometry>}
35112      * style - {Object}
35113      * 
35114      * Returns:
35115      * {String} The corresponding node type for the specified geometry
35116      */
35117     getNodeType: function(geometry, style) { },
35118
35119     /** 
35120      * Method: drawGeometry 
35121      * Draw the geometry, creating new nodes, setting paths, setting style,
35122      *     setting featureId on the node.  This method should only be called
35123      *     by the renderer itself.
35124      *
35125      * Parameters:
35126      * geometry - {<OpenLayers.Geometry>}
35127      * style - {Object}
35128      * featureId - {String}
35129      * 
35130      * Returns:
35131      * {Boolean} true if the geometry has been drawn completely; null if
35132      *     incomplete; false otherwise
35133      */
35134     drawGeometry: function(geometry, style, featureId) {
35135         var className = geometry.CLASS_NAME;
35136         var rendered = true;
35137         if ((className == "OpenLayers.Geometry.Collection") ||
35138             (className == "OpenLayers.Geometry.MultiPoint") ||
35139             (className == "OpenLayers.Geometry.MultiLineString") ||
35140             (className == "OpenLayers.Geometry.MultiPolygon")) {
35141             for (var i = 0, len=geometry.components.length; i<len; i++) {
35142                 rendered = this.drawGeometry(
35143                     geometry.components[i], style, featureId) && rendered;
35144             }
35145             return rendered;
35146         }
35147
35148         rendered = false;
35149         var removeBackground = false;
35150         if (style.display != "none") {
35151             if (style.backgroundGraphic) {
35152                 this.redrawBackgroundNode(geometry.id, geometry, style,
35153                     featureId);
35154             } else {
35155                 removeBackground = true;
35156             }
35157             rendered = this.redrawNode(geometry.id, geometry, style,
35158                 featureId);
35159         }
35160         if (rendered == false) {
35161             var node = document.getElementById(geometry.id);
35162             if (node) {
35163                 if (node._style.backgroundGraphic) {
35164                     removeBackground = true;
35165                 }
35166                 node.parentNode.removeChild(node);
35167             }
35168         }
35169         if (removeBackground) {
35170             var node = document.getElementById(
35171                 geometry.id + this.BACKGROUND_ID_SUFFIX);
35172             if (node) {
35173                 node.parentNode.removeChild(node);
35174             }
35175         }
35176         return rendered;
35177     },
35178     
35179     /**
35180      * Method: redrawNode
35181      * 
35182      * Parameters:
35183      * id - {String}
35184      * geometry - {<OpenLayers.Geometry>}
35185      * style - {Object}
35186      * featureId - {String}
35187      * 
35188      * Returns:
35189      * {Boolean} true if the complete geometry could be drawn, null if parts of
35190      *     the geometry could not be drawn, false otherwise
35191      */
35192     redrawNode: function(id, geometry, style, featureId) {
35193         style = this.applyDefaultSymbolizer(style);
35194         // Get the node if it's already on the map.
35195         var node = this.nodeFactory(id, this.getNodeType(geometry, style));
35196         
35197         // Set the data for the node, then draw it.
35198         node._featureId = featureId;
35199         node._boundsBottom = geometry.getBounds().bottom;
35200         node._geometryClass = geometry.CLASS_NAME;
35201         node._style = style;
35202
35203         var drawResult = this.drawGeometryNode(node, geometry, style);
35204         if(drawResult === false) {
35205             return false;
35206         }
35207          
35208         node = drawResult.node;
35209         
35210         // Insert the node into the indexer so it can show us where to
35211         // place it. Note that this operation is O(log(n)). If there's a
35212         // performance problem (when dragging, for instance) this is
35213         // likely where it would be.
35214         if (this.indexer) {
35215             var insert = this.indexer.insert(node);
35216             if (insert) {
35217                 this.vectorRoot.insertBefore(node, insert);
35218             } else {
35219                 this.vectorRoot.appendChild(node);
35220             }
35221         } else {
35222             // if there's no indexer, simply append the node to root,
35223             // but only if the node is a new one
35224             if (node.parentNode !== this.vectorRoot){ 
35225                 this.vectorRoot.appendChild(node);
35226             }
35227         }
35228         
35229         this.postDraw(node);
35230         
35231         return drawResult.complete;
35232     },
35233     
35234     /**
35235      * Method: redrawBackgroundNode
35236      * Redraws the node using special 'background' style properties. Basically
35237      *     just calls redrawNode(), but instead of directly using the 
35238      *     'externalGraphic', 'graphicXOffset', 'graphicYOffset', and 
35239      *     'graphicZIndex' properties directly from the specified 'style' 
35240      *     parameter, we create a new style object and set those properties 
35241      *     from the corresponding 'background'-prefixed properties from 
35242      *     specified 'style' parameter.
35243      * 
35244      * Parameters:
35245      * id - {String}
35246      * geometry - {<OpenLayers.Geometry>}
35247      * style - {Object}
35248      * featureId - {String}
35249      * 
35250      * Returns:
35251      * {Boolean} true if the complete geometry could be drawn, null if parts of
35252      *     the geometry could not be drawn, false otherwise
35253      */
35254     redrawBackgroundNode: function(id, geometry, style, featureId) {
35255         var backgroundStyle = OpenLayers.Util.extend({}, style);
35256         
35257         // Set regular style attributes to apply to the background styles.
35258         backgroundStyle.externalGraphic = backgroundStyle.backgroundGraphic;
35259         backgroundStyle.graphicXOffset = backgroundStyle.backgroundXOffset;
35260         backgroundStyle.graphicYOffset = backgroundStyle.backgroundYOffset;
35261         backgroundStyle.graphicZIndex = backgroundStyle.backgroundGraphicZIndex;
35262         backgroundStyle.graphicWidth = backgroundStyle.backgroundWidth || backgroundStyle.graphicWidth;
35263         backgroundStyle.graphicHeight = backgroundStyle.backgroundHeight || backgroundStyle.graphicHeight;
35264         
35265         // Erase background styles.
35266         backgroundStyle.backgroundGraphic = null;
35267         backgroundStyle.backgroundXOffset = null;
35268         backgroundStyle.backgroundYOffset = null;
35269         backgroundStyle.backgroundGraphicZIndex = null;
35270         
35271         return this.redrawNode(
35272             id + this.BACKGROUND_ID_SUFFIX, 
35273             geometry, 
35274             backgroundStyle, 
35275             null
35276         );
35277     },
35278
35279     /**
35280      * Method: drawGeometryNode
35281      * Given a node, draw a geometry on the specified layer.
35282      *     node and geometry are required arguments, style is optional.
35283      *     This method is only called by the render itself.
35284      *
35285      * Parameters:
35286      * node - {DOMElement}
35287      * geometry - {<OpenLayers.Geometry>}
35288      * style - {Object}
35289      * 
35290      * Returns:
35291      * {Object} a hash with properties "node" (the drawn node) and "complete"
35292      *     (null if parts of the geometry could not be drawn, false if nothing
35293      *     could be drawn)
35294      */
35295     drawGeometryNode: function(node, geometry, style) {
35296         style = style || node._style;
35297
35298         var options = {
35299             'isFilled': style.fill === undefined ?
35300                 true :
35301                 style.fill,
35302             'isStroked': style.stroke === undefined ?
35303                 !!style.strokeWidth :
35304                 style.stroke
35305         };
35306         var drawn;
35307         switch (geometry.CLASS_NAME) {
35308             case "OpenLayers.Geometry.Point":
35309                 if(style.graphic === false) {
35310                     options.isFilled = false;
35311                     options.isStroked = false;
35312                 }
35313                 drawn = this.drawPoint(node, geometry);
35314                 break;
35315             case "OpenLayers.Geometry.LineString":
35316                 options.isFilled = false;
35317                 drawn = this.drawLineString(node, geometry);
35318                 break;
35319             case "OpenLayers.Geometry.LinearRing":
35320                 drawn = this.drawLinearRing(node, geometry);
35321                 break;
35322             case "OpenLayers.Geometry.Polygon":
35323                 drawn = this.drawPolygon(node, geometry);
35324                 break;
35325             case "OpenLayers.Geometry.Rectangle":
35326                 drawn = this.drawRectangle(node, geometry);
35327                 break;
35328             default:
35329                 break;
35330         }
35331
35332         node._options = options; 
35333
35334         //set style
35335         //TBD simplify this
35336         if (drawn != false) {
35337             return {
35338                 node: this.setStyle(node, style, options, geometry),
35339                 complete: drawn
35340             };
35341         } else {
35342             return false;
35343         }
35344     },
35345     
35346     /**
35347      * Method: postDraw
35348      * Things that have do be done after the geometry node is appended
35349      *     to its parent node. To be overridden by subclasses.
35350      * 
35351      * Parameters:
35352      * node - {DOMElement}
35353      */
35354     postDraw: function(node) {},
35355     
35356     /**
35357      * Method: drawPoint
35358      * Virtual function for drawing Point Geometry. 
35359      *     Should be implemented by subclasses.
35360      *     This method is only called by the renderer itself.
35361      * 
35362      * Parameters: 
35363      * node - {DOMElement}
35364      * geometry - {<OpenLayers.Geometry>}
35365      * 
35366      * Returns:
35367      * {DOMElement} or false if the renderer could not draw the point
35368      */ 
35369     drawPoint: function(node, geometry) {},
35370
35371     /**
35372      * Method: drawLineString
35373      * Virtual function for drawing LineString Geometry. 
35374      *     Should be implemented by subclasses.
35375      *     This method is only called by the renderer itself.
35376      * 
35377      * Parameters: 
35378      * node - {DOMElement}
35379      * geometry - {<OpenLayers.Geometry>}
35380      * 
35381      * Returns:
35382      * {DOMElement} or null if the renderer could not draw all components of
35383      *     the linestring, or false if nothing could be drawn
35384      */ 
35385     drawLineString: function(node, geometry) {},
35386
35387     /**
35388      * Method: drawLinearRing
35389      * Virtual function for drawing LinearRing Geometry. 
35390      *     Should be implemented by subclasses.
35391      *     This method is only called by the renderer itself.
35392      * 
35393      * Parameters: 
35394      * node - {DOMElement}
35395      * geometry - {<OpenLayers.Geometry>}
35396      * 
35397      * Returns:
35398      * {DOMElement} or null if the renderer could not draw all components
35399      *     of the linear ring, or false if nothing could be drawn
35400      */ 
35401     drawLinearRing: function(node, geometry) {},
35402
35403     /**
35404      * Method: drawPolygon
35405      * Virtual function for drawing Polygon Geometry. 
35406      *    Should be implemented by subclasses.
35407      *    This method is only called by the renderer itself.
35408      * 
35409      * Parameters: 
35410      * node - {DOMElement}
35411      * geometry - {<OpenLayers.Geometry>}
35412      * 
35413      * Returns:
35414      * {DOMElement} or null if the renderer could not draw all components
35415      *     of the polygon, or false if nothing could be drawn
35416      */ 
35417     drawPolygon: function(node, geometry) {},
35418
35419     /**
35420      * Method: drawRectangle
35421      * Virtual function for drawing Rectangle Geometry. 
35422      *     Should be implemented by subclasses.
35423      *     This method is only called by the renderer itself.
35424      * 
35425      * Parameters: 
35426      * node - {DOMElement}
35427      * geometry - {<OpenLayers.Geometry>}
35428      * 
35429      * Returns:
35430      * {DOMElement} or false if the renderer could not draw the rectangle
35431      */ 
35432     drawRectangle: function(node, geometry) {},
35433
35434     /**
35435      * Method: drawCircle
35436      * Virtual function for drawing Circle Geometry. 
35437      *     Should be implemented by subclasses.
35438      *     This method is only called by the renderer itself.
35439      * 
35440      * Parameters: 
35441      * node - {DOMElement}
35442      * geometry - {<OpenLayers.Geometry>}
35443      * 
35444      * Returns:
35445      * {DOMElement} or false if the renderer could not draw the circle
35446      */ 
35447     drawCircle: function(node, geometry) {},
35448
35449     /**
35450      * Method: removeText
35451      * Removes a label
35452      * 
35453      * Parameters:
35454      * featureId - {String}
35455      */
35456     removeText: function(featureId) {
35457         var label = document.getElementById(featureId + this.LABEL_ID_SUFFIX);
35458         if (label) {
35459             this.textRoot.removeChild(label);
35460         }
35461         var outline = document.getElementById(featureId + this.LABEL_OUTLINE_SUFFIX);
35462         if (outline) {
35463             this.textRoot.removeChild(outline);
35464         }
35465     },
35466
35467     /**
35468      * Method: getFeatureIdFromEvent
35469      * 
35470      * Parameters:
35471      * evt - {Object} An <OpenLayers.Event> object
35472      *
35473      * Returns:
35474      * {String} A feature id or undefined.
35475      */
35476     getFeatureIdFromEvent: function(evt) {
35477         var target = evt.target;
35478         var useElement = target && target.correspondingUseElement;
35479         var node = useElement ? useElement : (target || evt.srcElement);
35480         return node._featureId;
35481     },
35482
35483     /** 
35484      * Method: eraseGeometry
35485      * Erase a geometry from the renderer. In the case of a multi-geometry, 
35486      *     we cycle through and recurse on ourselves. Otherwise, we look for a 
35487      *     node with the geometry.id, destroy its geometry, and remove it from
35488      *     the DOM.
35489      * 
35490      * Parameters:
35491      * geometry - {<OpenLayers.Geometry>}
35492      * featureId - {String}
35493      */
35494     eraseGeometry: function(geometry, featureId) {
35495         if ((geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPoint") ||
35496             (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiLineString") ||
35497             (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPolygon") ||
35498             (geometry.CLASS_NAME == "OpenLayers.Geometry.Collection")) {
35499             for (var i=0, len=geometry.components.length; i<len; i++) {
35500                 this.eraseGeometry(geometry.components[i], featureId);
35501             }
35502         } else {    
35503             var element = OpenLayers.Util.getElement(geometry.id);
35504             if (element && element.parentNode) {
35505                 if (element.geometry) {
35506                     element.geometry.destroy();
35507                     element.geometry = null;
35508                 }
35509                 element.parentNode.removeChild(element);
35510
35511                 if (this.indexer) {
35512                     this.indexer.remove(element);
35513                 }
35514                 
35515                 if (element._style.backgroundGraphic) {
35516                     var backgroundId = geometry.id + this.BACKGROUND_ID_SUFFIX;
35517                     var bElem = OpenLayers.Util.getElement(backgroundId);
35518                     if (bElem && bElem.parentNode) {
35519                         // No need to destroy the geometry since the element and the background
35520                         // node share the same geometry.
35521                         bElem.parentNode.removeChild(bElem);
35522                     }
35523                 }
35524             }
35525         }
35526     },
35527
35528     /** 
35529      * Method: nodeFactory
35530      * Create new node of the specified type, with the (optional) specified id.
35531      * 
35532      * If node already exists with same ID and a different type, we remove it
35533      *     and then call ourselves again to recreate it.
35534      * 
35535      * Parameters:
35536      * id - {String}
35537      * type - {String} type Kind of node to draw.
35538      * 
35539      * Returns:
35540      * {DOMElement} A new node of the given type and id.
35541      */
35542     nodeFactory: function(id, type) {
35543         var node = OpenLayers.Util.getElement(id);
35544         if (node) {
35545             if (!this.nodeTypeCompare(node, type)) {
35546                 node.parentNode.removeChild(node);
35547                 node = this.nodeFactory(id, type);
35548             }
35549         } else {
35550             node = this.createNode(type, id);
35551         }
35552         return node;
35553     },
35554     
35555     /** 
35556      * Method: nodeTypeCompare
35557      * 
35558      * Parameters:
35559      * node - {DOMElement}
35560      * type - {String} Kind of node
35561      * 
35562      * Returns:
35563      * {Boolean} Whether or not the specified node is of the specified type
35564      *     This function must be overridden by subclasses.
35565      */
35566     nodeTypeCompare: function(node, type) {},
35567     
35568     /** 
35569      * Method: createNode
35570      * 
35571      * Parameters:
35572      * type - {String} Kind of node to draw.
35573      * id - {String} Id for node.
35574      * 
35575      * Returns:
35576      * {DOMElement} A new node of the given type and id.
35577      *     This function must be overridden by subclasses.
35578      */
35579     createNode: function(type, id) {},
35580
35581     /**
35582      * Method: moveRoot
35583      * moves this renderer's root to a different renderer.
35584      * 
35585      * Parameters:
35586      * renderer - {<OpenLayers.Renderer>} target renderer for the moved root
35587      */
35588     moveRoot: function(renderer) {
35589         var root = this.root;
35590         if(renderer.root.parentNode == this.rendererRoot) {
35591             root = renderer.root;
35592         }
35593         root.parentNode.removeChild(root);
35594         renderer.rendererRoot.appendChild(root);
35595     },
35596     
35597     /**
35598      * Method: getRenderLayerId
35599      * Gets the layer that this renderer's output appears on. If moveRoot was
35600      * used, this will be different from the id of the layer containing the
35601      * features rendered by this renderer.
35602      * 
35603      * Returns:
35604      * {String} the id of the output layer.
35605      */
35606     getRenderLayerId: function() {
35607         return this.root.parentNode.parentNode.id;
35608     },
35609     
35610     /**
35611      * Method: isComplexSymbol
35612      * Determines if a symbol cannot be rendered using drawCircle
35613      * 
35614      * Parameters:
35615      * graphicName - {String}
35616      * 
35617      * Returns
35618      * {Boolean} true if the symbol is complex, false if not
35619      */
35620     isComplexSymbol: function(graphicName) {
35621         return (graphicName != "circle") && !!graphicName;
35622     },
35623
35624     CLASS_NAME: "OpenLayers.Renderer.Elements"
35625 });
35626
35627 /* ======================================================================
35628     OpenLayers/Renderer/SVG.js
35629    ====================================================================== */
35630
35631 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
35632  * full list of contributors). Published under the 2-clause BSD license.
35633  * See license.txt in the OpenLayers distribution or repository for the
35634  * full text of the license. */
35635
35636 /**
35637  * @requires OpenLayers/Renderer/Elements.js
35638  */
35639
35640 /**
35641  * Class: OpenLayers.Renderer.SVG
35642  * 
35643  * Inherits:
35644  *  - <OpenLayers.Renderer.Elements>
35645  */
35646 OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
35647
35648     /** 
35649      * Property: xmlns
35650      * {String}
35651      */
35652     xmlns: "http://www.w3.org/2000/svg",
35653     
35654     /**
35655      * Property: xlinkns
35656      * {String}
35657      */
35658     xlinkns: "http://www.w3.org/1999/xlink",
35659
35660     /**
35661      * Constant: MAX_PIXEL
35662      * {Integer} Firefox has a limitation where values larger or smaller than  
35663      *           about 15000 in an SVG document lock the browser up. This 
35664      *           works around it.
35665      */
35666     MAX_PIXEL: 15000,
35667
35668     /**
35669      * Property: translationParameters
35670      * {Object} Hash with "x" and "y" properties
35671      */
35672     translationParameters: null,
35673     
35674     /**
35675      * Property: symbolMetrics
35676      * {Object} Cache for symbol metrics according to their svg coordinate
35677      *     space. This is an object keyed by the symbol's id, and values are
35678      *     an array of [width, centerX, centerY].
35679      */
35680     symbolMetrics: null,
35681     
35682     /**
35683      * Constructor: OpenLayers.Renderer.SVG
35684      * 
35685      * Parameters:
35686      * containerID - {String}
35687      */
35688     initialize: function(containerID) {
35689         if (!this.supported()) { 
35690             return; 
35691         }
35692         OpenLayers.Renderer.Elements.prototype.initialize.apply(this, 
35693                                                                 arguments);
35694         this.translationParameters = {x: 0, y: 0};
35695         
35696         this.symbolMetrics = {};
35697     },
35698
35699     /**
35700      * APIMethod: supported
35701      * 
35702      * Returns:
35703      * {Boolean} Whether or not the browser supports the SVG renderer
35704      */
35705     supported: function() {
35706         var svgFeature = "http://www.w3.org/TR/SVG11/feature#";
35707         return (document.implementation && 
35708            (document.implementation.hasFeature("org.w3c.svg", "1.0") || 
35709             document.implementation.hasFeature(svgFeature + "SVG", "1.1") || 
35710             document.implementation.hasFeature(svgFeature + "BasicStructure", "1.1") ));
35711     },    
35712
35713     /**
35714      * Method: inValidRange
35715      * See #669 for more information
35716      *
35717      * Parameters:
35718      * x      - {Integer}
35719      * y      - {Integer}
35720      * xyOnly - {Boolean} whether or not to just check for x and y, which means
35721      *     to not take the current translation parameters into account if true.
35722      * 
35723      * Returns:
35724      * {Boolean} Whether or not the 'x' and 'y' coordinates are in the  
35725      *           valid range.
35726      */ 
35727     inValidRange: function(x, y, xyOnly) {
35728         var left = x + (xyOnly ? 0 : this.translationParameters.x);
35729         var top = y + (xyOnly ? 0 : this.translationParameters.y);
35730         return (left >= -this.MAX_PIXEL && left <= this.MAX_PIXEL &&
35731                 top >= -this.MAX_PIXEL && top <= this.MAX_PIXEL);
35732     },
35733
35734     /**
35735      * Method: setExtent
35736      * 
35737      * Parameters:
35738      * extent - {<OpenLayers.Bounds>}
35739      * resolutionChanged - {Boolean}
35740      * 
35741      * Returns:
35742      * {Boolean} true to notify the layer that the new extent does not exceed
35743      *     the coordinate range, and the features will not need to be redrawn.
35744      *     False otherwise.
35745      */
35746     setExtent: function(extent, resolutionChanged) {
35747         var coordSysUnchanged = OpenLayers.Renderer.Elements.prototype.setExtent.apply(this, arguments);
35748         
35749         var resolution = this.getResolution(),
35750             left = -extent.left / resolution,
35751             top = extent.top / resolution;
35752
35753         // If the resolution has changed, start over changing the corner, because
35754         // the features will redraw.
35755         if (resolutionChanged) {
35756             this.left = left;
35757             this.top = top;
35758             // Set the viewbox
35759             var extentString = "0 0 " + this.size.w + " " + this.size.h;
35760
35761             this.rendererRoot.setAttributeNS(null, "viewBox", extentString);
35762             this.translate(this.xOffset, 0);
35763             return true;
35764         } else {
35765             var inRange = this.translate(left - this.left + this.xOffset, top - this.top);
35766             if (!inRange) {
35767                 // recenter the coordinate system
35768                 this.setExtent(extent, true);
35769             }
35770             return coordSysUnchanged && inRange;
35771         }
35772     },
35773     
35774     /**
35775      * Method: translate
35776      * Transforms the SVG coordinate system
35777      * 
35778      * Parameters:
35779      * x - {Float}
35780      * y - {Float}
35781      * 
35782      * Returns:
35783      * {Boolean} true if the translation parameters are in the valid coordinates
35784      *     range, false otherwise.
35785      */
35786     translate: function(x, y) {
35787         if (!this.inValidRange(x, y, true)) {
35788             return false;
35789         } else {
35790             var transformString = "";
35791             if (x || y) {
35792                 transformString = "translate(" + x + "," + y + ")";
35793             }
35794             this.root.setAttributeNS(null, "transform", transformString);
35795             this.translationParameters = {x: x, y: y};
35796             return true;
35797         }
35798     },
35799
35800     /**
35801      * Method: setSize
35802      * Sets the size of the drawing surface.
35803      * 
35804      * Parameters:
35805      * size - {<OpenLayers.Size>} The size of the drawing surface
35806      */
35807     setSize: function(size) {
35808         OpenLayers.Renderer.prototype.setSize.apply(this, arguments);
35809         
35810         this.rendererRoot.setAttributeNS(null, "width", this.size.w);
35811         this.rendererRoot.setAttributeNS(null, "height", this.size.h);
35812     },
35813
35814     /** 
35815      * Method: getNodeType 
35816      * 
35817      * Parameters:
35818      * geometry - {<OpenLayers.Geometry>}
35819      * style - {Object}
35820      * 
35821      * Returns:
35822      * {String} The corresponding node type for the specified geometry
35823      */
35824     getNodeType: function(geometry, style) {
35825         var nodeType = null;
35826         switch (geometry.CLASS_NAME) {
35827             case "OpenLayers.Geometry.Point":
35828                 if (style.externalGraphic) {
35829                     nodeType = "image";
35830                 } else if (this.isComplexSymbol(style.graphicName)) {
35831                     nodeType = "svg";
35832                 } else {
35833                     nodeType = "circle";
35834                 }
35835                 break;
35836             case "OpenLayers.Geometry.Rectangle":
35837                 nodeType = "rect";
35838                 break;
35839             case "OpenLayers.Geometry.LineString":
35840                 nodeType = "polyline";
35841                 break;
35842             case "OpenLayers.Geometry.LinearRing":
35843                 nodeType = "polygon";
35844                 break;
35845             case "OpenLayers.Geometry.Polygon":
35846             case "OpenLayers.Geometry.Curve":
35847                 nodeType = "path";
35848                 break;
35849             default:
35850                 break;
35851         }
35852         return nodeType;
35853     },
35854
35855     /** 
35856      * Method: setStyle
35857      * Use to set all the style attributes to a SVG node.
35858      * 
35859      * Takes care to adjust stroke width and point radius to be
35860      * resolution-relative
35861      *
35862      * Parameters:
35863      * node - {SVGDomElement} An SVG element to decorate
35864      * style - {Object}
35865      * options - {Object} Currently supported options include 
35866      *                              'isFilled' {Boolean} and
35867      *                              'isStroked' {Boolean}
35868      */
35869     setStyle: function(node, style, options) {
35870         style = style  || node._style;
35871         options = options || node._options;
35872
35873         var title = style.title || style.graphicTitle;
35874         if (title) {
35875             node.setAttributeNS(null, "title", title);
35876             //Standards-conformant SVG
35877             // Prevent duplicate nodes. See issue https://github.com/openlayers/ol2/issues/92 
35878             var titleNode = node.getElementsByTagName("title");
35879             if (titleNode.length > 0) {
35880                 titleNode[0].firstChild.textContent = title;
35881             } else {
35882                 var label = this.nodeFactory(null, "title");
35883                 label.textContent = title;
35884                 node.appendChild(label);
35885             }
35886         }
35887
35888         var r = parseFloat(node.getAttributeNS(null, "r"));
35889         var widthFactor = 1;
35890         var pos;
35891         if (node._geometryClass == "OpenLayers.Geometry.Point" && r) {
35892             node.style.visibility = "";
35893             if (style.graphic === false) {
35894                 node.style.visibility = "hidden";
35895             } else if (style.externalGraphic) {
35896                 pos = this.getPosition(node);
35897                 if (style.graphicWidth && style.graphicHeight) {
35898                   node.setAttributeNS(null, "preserveAspectRatio", "none");
35899                 }
35900                 var width = style.graphicWidth || style.graphicHeight;
35901                 var height = style.graphicHeight || style.graphicWidth;
35902                 width = width ? width : style.pointRadius*2;
35903                 height = height ? height : style.pointRadius*2;
35904                 var xOffset = (style.graphicXOffset != undefined) ?
35905                     style.graphicXOffset : -(0.5 * width);
35906                 var yOffset = (style.graphicYOffset != undefined) ?
35907                     style.graphicYOffset : -(0.5 * height);
35908
35909                 var opacity = style.graphicOpacity || style.fillOpacity;
35910                 
35911                 node.setAttributeNS(null, "x", (pos.x + xOffset).toFixed());
35912                 node.setAttributeNS(null, "y", (pos.y + yOffset).toFixed());
35913                 node.setAttributeNS(null, "width", width);
35914                 node.setAttributeNS(null, "height", height);
35915                 node.setAttributeNS(this.xlinkns, "xlink:href", style.externalGraphic);
35916                 node.setAttributeNS(null, "style", "opacity: "+opacity);
35917                 node.onclick = OpenLayers.Event.preventDefault;
35918             } else if (this.isComplexSymbol(style.graphicName)) {
35919                 // the symbol viewBox is three times as large as the symbol
35920                 var offset = style.pointRadius * 3;
35921                 var size = offset * 2;
35922                 var src = this.importSymbol(style.graphicName);
35923                 pos = this.getPosition(node);
35924                 widthFactor = this.symbolMetrics[src.id][0] * 3 / size;
35925                 
35926                 // remove the node from the dom before we modify it. This
35927                 // prevents various rendering issues in Safari and FF
35928                 var parent = node.parentNode;
35929                 var nextSibling = node.nextSibling;
35930                 if(parent) {
35931                     parent.removeChild(node);
35932                 }
35933                 
35934                 // The more appropriate way to implement this would be use/defs,
35935                 // but due to various issues in several browsers, it is safer to
35936                 // copy the symbols instead of referencing them. 
35937                 // See e.g. ticket http://trac.osgeo.org/openlayers/ticket/2985 
35938                 // and this email thread
35939                 // http://osgeo-org.1803224.n2.nabble.com/Select-Control-Ctrl-click-on-Feature-with-a-graphicName-opens-new-browser-window-tc5846039.html
35940                 node.firstChild && node.removeChild(node.firstChild);
35941                 node.appendChild(src.firstChild.cloneNode(true));
35942                 node.setAttributeNS(null, "viewBox", src.getAttributeNS(null, "viewBox"));
35943                 
35944                 node.setAttributeNS(null, "width", size);
35945                 node.setAttributeNS(null, "height", size);
35946                 node.setAttributeNS(null, "x", pos.x - offset);
35947                 node.setAttributeNS(null, "y", pos.y - offset);
35948                 
35949                 // now that the node has all its new properties, insert it
35950                 // back into the dom where it was
35951                 if(nextSibling) {
35952                     parent.insertBefore(node, nextSibling);
35953                 } else if(parent) {
35954                     parent.appendChild(node);
35955                 }
35956             } else {
35957                 node.setAttributeNS(null, "r", style.pointRadius);
35958             }
35959
35960             var rotation = style.rotation;
35961             
35962             if ((rotation !== undefined || node._rotation !== undefined) && pos) {
35963                 node._rotation = rotation;
35964                 rotation |= 0;
35965                 if (node.nodeName !== "svg") { 
35966                     node.setAttributeNS(null, "transform", 
35967                         "rotate(" + rotation + " " + pos.x + " " + 
35968                         pos.y + ")"); 
35969                 } else {
35970                     var metrics = this.symbolMetrics[src.id];
35971                     node.firstChild.setAttributeNS(null, "transform", "rotate(" 
35972                         + rotation + " " 
35973                         + metrics[1] + " "
35974                         + metrics[2] + ")");
35975                 }
35976             }
35977         }
35978         
35979         if (options.isFilled) {
35980             node.setAttributeNS(null, "fill", style.fillColor);
35981             node.setAttributeNS(null, "fill-opacity", style.fillOpacity);
35982         } else {
35983             node.setAttributeNS(null, "fill", "none");
35984         }
35985
35986         if (options.isStroked) {
35987             node.setAttributeNS(null, "stroke", style.strokeColor);
35988             node.setAttributeNS(null, "stroke-opacity", style.strokeOpacity);
35989             node.setAttributeNS(null, "stroke-width", style.strokeWidth * widthFactor);
35990             node.setAttributeNS(null, "stroke-linecap", style.strokeLinecap || "round");
35991             // Hard-coded linejoin for now, to make it look the same as in VML.
35992             // There is no strokeLinejoin property yet for symbolizers.
35993             node.setAttributeNS(null, "stroke-linejoin", "round");
35994             style.strokeDashstyle && node.setAttributeNS(null,
35995                 "stroke-dasharray", this.dashStyle(style, widthFactor));
35996         } else {
35997             node.setAttributeNS(null, "stroke", "none");
35998         }
35999         
36000         if (style.pointerEvents) {
36001             node.setAttributeNS(null, "pointer-events", style.pointerEvents);
36002         }
36003                 
36004         if (style.cursor != null) {
36005             node.setAttributeNS(null, "cursor", style.cursor);
36006         }
36007         
36008         return node;
36009     },
36010
36011     /** 
36012      * Method: dashStyle
36013      * 
36014      * Parameters:
36015      * style - {Object}
36016      * widthFactor - {Number}
36017      * 
36018      * Returns:
36019      * {String} A SVG compliant 'stroke-dasharray' value
36020      */
36021     dashStyle: function(style, widthFactor) {
36022         var w = style.strokeWidth * widthFactor;
36023         var str = style.strokeDashstyle;
36024         switch (str) {
36025             case 'solid':
36026                 return 'none';
36027             case 'dot':
36028                 return [1, 4 * w].join();
36029             case 'dash':
36030                 return [4 * w, 4 * w].join();
36031             case 'dashdot':
36032                 return [4 * w, 4 * w, 1, 4 * w].join();
36033             case 'longdash':
36034                 return [8 * w, 4 * w].join();
36035             case 'longdashdot':
36036                 return [8 * w, 4 * w, 1, 4 * w].join();
36037             default:
36038                 return OpenLayers.String.trim(str).replace(/\s+/g, ",");
36039         }
36040     },
36041     
36042     /** 
36043      * Method: createNode
36044      * 
36045      * Parameters:
36046      * type - {String} Kind of node to draw
36047      * id - {String} Id for node
36048      * 
36049      * Returns:
36050      * {DOMElement} A new node of the given type and id
36051      */
36052     createNode: function(type, id) {
36053         var node = document.createElementNS(this.xmlns, type);
36054         if (id) {
36055             node.setAttributeNS(null, "id", id);
36056         }
36057         return node;    
36058     },
36059     
36060     /** 
36061      * Method: nodeTypeCompare
36062      * 
36063      * Parameters:
36064      * node - {SVGDomElement} An SVG element
36065      * type - {String} Kind of node
36066      * 
36067      * Returns:
36068      * {Boolean} Whether or not the specified node is of the specified type
36069      */
36070     nodeTypeCompare: function(node, type) {
36071         return (type == node.nodeName);
36072     },
36073    
36074     /**
36075      * Method: createRenderRoot
36076      * 
36077      * Returns:
36078      * {DOMElement} The specific render engine's root element
36079      */
36080     createRenderRoot: function() {
36081         var svg = this.nodeFactory(this.container.id + "_svgRoot", "svg");
36082         svg.style.display = "block";
36083         return svg;
36084     },
36085
36086     /**
36087      * Method: createRoot
36088      * 
36089      * Parameters:
36090      * suffix - {String} suffix to append to the id
36091      * 
36092      * Returns:
36093      * {DOMElement}
36094      */
36095     createRoot: function(suffix) {
36096         return this.nodeFactory(this.container.id + suffix, "g");
36097     },
36098
36099     /**
36100      * Method: createDefs
36101      *
36102      * Returns:
36103      * {DOMElement} The element to which we'll add the symbol definitions
36104      */
36105     createDefs: function() {
36106         var defs = this.nodeFactory(this.container.id + "_defs", "defs");
36107         this.rendererRoot.appendChild(defs);
36108         return defs;
36109     },
36110
36111     /**************************************
36112      *                                    *
36113      *     GEOMETRY DRAWING FUNCTIONS     *
36114      *                                    *
36115      **************************************/
36116
36117     /**
36118      * Method: drawPoint
36119      * This method is only called by the renderer itself.
36120      * 
36121      * Parameters: 
36122      * node - {DOMElement}
36123      * geometry - {<OpenLayers.Geometry>}
36124      * 
36125      * Returns:
36126      * {DOMElement} or false if the renderer could not draw the point
36127      */ 
36128     drawPoint: function(node, geometry) {
36129         return this.drawCircle(node, geometry, 1);
36130     },
36131
36132     /**
36133      * Method: drawCircle
36134      * This method is only called by the renderer itself.
36135      * 
36136      * Parameters: 
36137      * node - {DOMElement}
36138      * geometry - {<OpenLayers.Geometry>}
36139      * radius - {Float}
36140      * 
36141      * Returns:
36142      * {DOMElement} or false if the renderer could not draw the circle
36143      */
36144     drawCircle: function(node, geometry, radius) {
36145         var resolution = this.getResolution();
36146         var x = ((geometry.x - this.featureDx) / resolution + this.left);
36147         var y = (this.top - geometry.y / resolution);
36148
36149         if (this.inValidRange(x, y)) { 
36150             node.setAttributeNS(null, "cx", x);
36151             node.setAttributeNS(null, "cy", y);
36152             node.setAttributeNS(null, "r", radius);
36153             return node;
36154         } else {
36155             return false;
36156         }    
36157             
36158     },
36159     
36160     /**
36161      * Method: drawLineString
36162      * This method is only called by the renderer itself.
36163      * 
36164      * Parameters: 
36165      * node - {DOMElement}
36166      * geometry - {<OpenLayers.Geometry>}
36167      * 
36168      * Returns:
36169      * {DOMElement} or null if the renderer could not draw all components of
36170      *     the linestring, or false if nothing could be drawn
36171      */ 
36172     drawLineString: function(node, geometry) {
36173         var componentsResult = this.getComponentsString(geometry.components);
36174         if (componentsResult.path) {
36175             node.setAttributeNS(null, "points", componentsResult.path);
36176             return (componentsResult.complete ? node : null);  
36177         } else {
36178             return false;
36179         }
36180     },
36181     
36182     /**
36183      * Method: drawLinearRing
36184      * This method is only called by the renderer itself.
36185      * 
36186      * Parameters: 
36187      * node - {DOMElement}
36188      * geometry - {<OpenLayers.Geometry>}
36189      * 
36190      * Returns:
36191      * {DOMElement} or null if the renderer could not draw all components
36192      *     of the linear ring, or false if nothing could be drawn
36193      */ 
36194     drawLinearRing: function(node, geometry) {
36195         var componentsResult = this.getComponentsString(geometry.components);
36196         if (componentsResult.path) {
36197             node.setAttributeNS(null, "points", componentsResult.path);
36198             return (componentsResult.complete ? node : null);  
36199         } else {
36200             return false;
36201         }
36202     },
36203     
36204     /**
36205      * Method: drawPolygon
36206      * This method is only called by the renderer itself.
36207      * 
36208      * Parameters: 
36209      * node - {DOMElement}
36210      * geometry - {<OpenLayers.Geometry>}
36211      * 
36212      * Returns:
36213      * {DOMElement} or null if the renderer could not draw all components
36214      *     of the polygon, or false if nothing could be drawn
36215      */ 
36216     drawPolygon: function(node, geometry) {
36217         var d = "";
36218         var draw = true;
36219         var complete = true;
36220         var linearRingResult, path;
36221         for (var j=0, len=geometry.components.length; j<len; j++) {
36222             d += " M";
36223             linearRingResult = this.getComponentsString(
36224                 geometry.components[j].components, " ");
36225             path = linearRingResult.path;
36226             if (path) {
36227                 d += " " + path;
36228                 complete = linearRingResult.complete && complete;
36229             } else {
36230                 draw = false;
36231             }
36232         }
36233         d += " z";
36234         if (draw) {
36235             node.setAttributeNS(null, "d", d);
36236             node.setAttributeNS(null, "fill-rule", "evenodd");
36237             return complete ? node : null;
36238         } else {
36239             return false;
36240         }    
36241     },
36242     
36243     /**
36244      * Method: drawRectangle
36245      * This method is only called by the renderer itself.
36246      * 
36247      * Parameters: 
36248      * node - {DOMElement}
36249      * geometry - {<OpenLayers.Geometry>}
36250      * 
36251      * Returns:
36252      * {DOMElement} or false if the renderer could not draw the rectangle
36253      */ 
36254     drawRectangle: function(node, geometry) {
36255         var resolution = this.getResolution();
36256         var x = ((geometry.x - this.featureDx) / resolution + this.left);
36257         var y = (this.top - geometry.y / resolution);
36258
36259         if (this.inValidRange(x, y)) { 
36260             node.setAttributeNS(null, "x", x);
36261             node.setAttributeNS(null, "y", y);
36262             node.setAttributeNS(null, "width", geometry.width / resolution);
36263             node.setAttributeNS(null, "height", geometry.height / resolution);
36264             return node;
36265         } else {
36266             return false;
36267         }
36268     },
36269     
36270     /**
36271      * Method: drawText
36272      * This method is only called by the renderer itself.
36273      *
36274      * Parameters:
36275      * featureId - {String}
36276      * style -
36277      * location - {<OpenLayers.Geometry.Point>}
36278      */
36279     drawText: function(featureId, style, location) {
36280         var drawOutline = (!!style.labelOutlineWidth);
36281         // First draw text in halo color and size and overlay the
36282         // normal text afterwards
36283         if (drawOutline) {
36284             var outlineStyle = OpenLayers.Util.extend({}, style);
36285             outlineStyle.fontColor = outlineStyle.labelOutlineColor;
36286             outlineStyle.fontStrokeColor = outlineStyle.labelOutlineColor;
36287             outlineStyle.fontStrokeWidth = style.labelOutlineWidth;
36288             if (style.labelOutlineOpacity) {
36289                 outlineStyle.fontOpacity = style.labelOutlineOpacity;
36290             }
36291             delete outlineStyle.labelOutlineWidth;
36292             this.drawText(featureId, outlineStyle, location);
36293         }
36294
36295         var resolution = this.getResolution();
36296
36297         var x = ((location.x - this.featureDx) / resolution + this.left);
36298         var y = (location.y / resolution - this.top);
36299
36300         var suffix = (drawOutline)?this.LABEL_OUTLINE_SUFFIX:this.LABEL_ID_SUFFIX;
36301         var label = this.nodeFactory(featureId + suffix, "text");
36302
36303         label.setAttributeNS(null, "x", x);
36304         label.setAttributeNS(null, "y", -y);
36305
36306         if (style.fontColor) {
36307             label.setAttributeNS(null, "fill", style.fontColor);
36308         }
36309         if (style.fontStrokeColor) {
36310             label.setAttributeNS(null, "stroke", style.fontStrokeColor);
36311         }
36312         if (style.fontStrokeWidth) {
36313             label.setAttributeNS(null, "stroke-width", style.fontStrokeWidth);
36314         }
36315         if (style.fontOpacity) {
36316             label.setAttributeNS(null, "opacity", style.fontOpacity);
36317         }
36318         if (style.fontFamily) {
36319             label.setAttributeNS(null, "font-family", style.fontFamily);
36320         }
36321         if (style.fontSize) {
36322             label.setAttributeNS(null, "font-size", style.fontSize);
36323         }
36324         if (style.fontWeight) {
36325             label.setAttributeNS(null, "font-weight", style.fontWeight);
36326         }
36327         if (style.fontStyle) {
36328             label.setAttributeNS(null, "font-style", style.fontStyle);
36329         }
36330         if (style.labelSelect === true) {
36331             label.setAttributeNS(null, "pointer-events", "visible");
36332             label._featureId = featureId;
36333         } else {
36334             label.setAttributeNS(null, "pointer-events", "none");
36335         }
36336         var align = style.labelAlign || OpenLayers.Renderer.defaultSymbolizer.labelAlign;
36337         label.setAttributeNS(null, "text-anchor",
36338             OpenLayers.Renderer.SVG.LABEL_ALIGN[align[0]] || "middle");
36339
36340         if (OpenLayers.IS_GECKO === true) {
36341             label.setAttributeNS(null, "dominant-baseline",
36342                 OpenLayers.Renderer.SVG.LABEL_ALIGN[align[1]] || "central");
36343         }
36344
36345         var labelRows = style.label.split('\n');
36346         var numRows = labelRows.length;
36347         while (label.childNodes.length > numRows) {
36348             label.removeChild(label.lastChild);
36349         }
36350         for (var i = 0; i < numRows; i++) {
36351             var tspan = this.nodeFactory(featureId + suffix + "_tspan_" + i, "tspan");
36352             if (style.labelSelect === true) {
36353                 tspan._featureId = featureId;
36354                 tspan._geometry = location;
36355                 tspan._geometryClass = location.CLASS_NAME;
36356             }
36357             if (OpenLayers.IS_GECKO === false) {
36358                 tspan.setAttributeNS(null, "baseline-shift",
36359                     OpenLayers.Renderer.SVG.LABEL_VSHIFT[align[1]] || "-35%");
36360             }
36361             tspan.setAttribute("x", x);
36362             if (i == 0) {
36363                 var vfactor = OpenLayers.Renderer.SVG.LABEL_VFACTOR[align[1]];
36364                 if (vfactor == null) {
36365                      vfactor = -.5;
36366                 }
36367                 tspan.setAttribute("dy", (vfactor*(numRows-1)) + "em");
36368             } else {
36369                 tspan.setAttribute("dy", "1em");
36370             }
36371             tspan.textContent = (labelRows[i] === '') ? ' ' : labelRows[i];
36372             if (!tspan.parentNode) {
36373                 label.appendChild(tspan);
36374             }
36375         }
36376
36377         if (!label.parentNode) {
36378             this.textRoot.appendChild(label);
36379         }
36380     },
36381     
36382     /** 
36383      * Method: getComponentString
36384      * 
36385      * Parameters:
36386      * components - {Array(<OpenLayers.Geometry.Point>)} Array of points
36387      * separator - {String} character between coordinate pairs. Defaults to ","
36388      * 
36389      * Returns:
36390      * {Object} hash with properties "path" (the string created from the
36391      *     components and "complete" (false if the renderer was unable to
36392      *     draw all components)
36393      */
36394     getComponentsString: function(components, separator) {
36395         var renderCmp = [];
36396         var complete = true;
36397         var len = components.length;
36398         var strings = [];
36399         var str, component;
36400         for(var i=0; i<len; i++) {
36401             component = components[i];
36402             renderCmp.push(component);
36403             str = this.getShortString(component);
36404             if (str) {
36405                 strings.push(str);
36406             } else {
36407                 // The current component is outside the valid range. Let's
36408                 // see if the previous or next component is inside the range.
36409                 // If so, add the coordinate of the intersection with the
36410                 // valid range bounds.
36411                 if (i > 0) {
36412                     if (this.getShortString(components[i - 1])) {
36413                         strings.push(this.clipLine(components[i],
36414                             components[i-1]));
36415                     }
36416                 }
36417                 if (i < len - 1) {
36418                     if (this.getShortString(components[i + 1])) {
36419                         strings.push(this.clipLine(components[i],
36420                             components[i+1]));
36421                     }
36422                 }
36423                 complete = false;
36424             }
36425         }
36426
36427         return {
36428             path: strings.join(separator || ","),
36429             complete: complete
36430         };
36431     },
36432     
36433     /**
36434      * Method: clipLine
36435      * Given two points (one inside the valid range, and one outside),
36436      * clips the line betweeen the two points so that the new points are both
36437      * inside the valid range.
36438      * 
36439      * Parameters:
36440      * badComponent - {<OpenLayers.Geometry.Point>} original geometry of the
36441      *     invalid point
36442      * goodComponent - {<OpenLayers.Geometry.Point>} original geometry of the
36443      *     valid point
36444      * Returns
36445      * {String} the SVG coordinate pair of the clipped point (like
36446      *     getShortString), or an empty string if both passed componets are at
36447      *     the same point.
36448      */
36449     clipLine: function(badComponent, goodComponent) {
36450         if (goodComponent.equals(badComponent)) {
36451             return "";
36452         }
36453         var resolution = this.getResolution();
36454         var maxX = this.MAX_PIXEL - this.translationParameters.x;
36455         var maxY = this.MAX_PIXEL - this.translationParameters.y;
36456         var x1 = (goodComponent.x - this.featureDx) / resolution + this.left;
36457         var y1 = this.top - goodComponent.y / resolution;
36458         var x2 = (badComponent.x - this.featureDx) / resolution + this.left;
36459         var y2 = this.top - badComponent.y / resolution;
36460         var k;
36461         if (x2 < -maxX || x2 > maxX) {
36462             k = (y2 - y1) / (x2 - x1);
36463             x2 = x2 < 0 ? -maxX : maxX;
36464             y2 = y1 + (x2 - x1) * k;
36465         }
36466         if (y2 < -maxY || y2 > maxY) {
36467             k = (x2 - x1) / (y2 - y1);
36468             y2 = y2 < 0 ? -maxY : maxY;
36469             x2 = x1 + (y2 - y1) * k;
36470         }
36471         return x2 + "," + y2;
36472     },
36473
36474     /** 
36475      * Method: getShortString
36476      * 
36477      * Parameters:
36478      * point - {<OpenLayers.Geometry.Point>}
36479      * 
36480      * Returns:
36481      * {String} or false if point is outside the valid range
36482      */
36483     getShortString: function(point) {
36484         var resolution = this.getResolution();
36485         var x = ((point.x - this.featureDx) / resolution + this.left);
36486         var y = (this.top - point.y / resolution);
36487
36488         if (this.inValidRange(x, y)) { 
36489             return x + "," + y;
36490         } else {
36491             return false;
36492         }
36493     },
36494     
36495     /**
36496      * Method: getPosition
36497      * Finds the position of an svg node.
36498      * 
36499      * Parameters:
36500      * node - {DOMElement}
36501      * 
36502      * Returns:
36503      * {Object} hash with x and y properties, representing the coordinates
36504      *     within the svg coordinate system
36505      */
36506     getPosition: function(node) {
36507         return({
36508             x: parseFloat(node.getAttributeNS(null, "cx")),
36509             y: parseFloat(node.getAttributeNS(null, "cy"))
36510         });
36511     },
36512
36513     /**
36514      * Method: importSymbol
36515      * add a new symbol definition from the rendererer's symbol hash
36516      * 
36517      * Parameters:
36518      * graphicName - {String} name of the symbol to import
36519      * 
36520      * Returns:
36521      * {DOMElement} - the imported symbol
36522      */      
36523     importSymbol: function (graphicName)  {
36524         if (!this.defs) {
36525             // create svg defs tag
36526             this.defs = this.createDefs();
36527         }
36528         var id = this.container.id + "-" + graphicName;
36529         
36530         // check if symbol already exists in the defs
36531         var existing = document.getElementById(id);
36532         if (existing != null) {
36533             return existing;
36534         }
36535         
36536         var symbol = OpenLayers.Renderer.symbol[graphicName];
36537         if (!symbol) {
36538             throw new Error(graphicName + ' is not a valid symbol name');
36539         }
36540
36541         var symbolNode = this.nodeFactory(id, "symbol");
36542         var node = this.nodeFactory(null, "polygon");
36543         symbolNode.appendChild(node);
36544         var symbolExtent = new OpenLayers.Bounds(
36545                                     Number.MAX_VALUE, Number.MAX_VALUE, 0, 0);
36546
36547         var points = [];
36548         var x,y;
36549         for (var i=0; i<symbol.length; i=i+2) {
36550             x = symbol[i];
36551             y = symbol[i+1];
36552             symbolExtent.left = Math.min(symbolExtent.left, x);
36553             symbolExtent.bottom = Math.min(symbolExtent.bottom, y);
36554             symbolExtent.right = Math.max(symbolExtent.right, x);
36555             symbolExtent.top = Math.max(symbolExtent.top, y);
36556             points.push(x, ",", y);
36557         }
36558         
36559         node.setAttributeNS(null, "points", points.join(" "));
36560         
36561         var width = symbolExtent.getWidth();
36562         var height = symbolExtent.getHeight();
36563         // create a viewBox three times as large as the symbol itself,
36564         // to allow for strokeWidth being displayed correctly at the corners.
36565         var viewBox = [symbolExtent.left - width,
36566                         symbolExtent.bottom - height, width * 3, height * 3];
36567         symbolNode.setAttributeNS(null, "viewBox", viewBox.join(" "));
36568         this.symbolMetrics[id] = [
36569             Math.max(width, height),
36570             symbolExtent.getCenterLonLat().lon,
36571             symbolExtent.getCenterLonLat().lat
36572         ];
36573         
36574         this.defs.appendChild(symbolNode);
36575         return symbolNode;
36576     },
36577     
36578     /**
36579      * Method: getFeatureIdFromEvent
36580      * 
36581      * Parameters:
36582      * evt - {Object} An <OpenLayers.Event> object
36583      *
36584      * Returns:
36585      * {String} A feature id or undefined.
36586      */
36587     getFeatureIdFromEvent: function(evt) {
36588         var featureId = OpenLayers.Renderer.Elements.prototype.getFeatureIdFromEvent.apply(this, arguments);
36589         if(!featureId) {
36590             var target = evt.target;
36591             featureId = target.parentNode && target != this.rendererRoot ?
36592                 target.parentNode._featureId : undefined;
36593         }
36594         return featureId;
36595     },
36596
36597     CLASS_NAME: "OpenLayers.Renderer.SVG"
36598 });
36599
36600 /**
36601  * Constant: OpenLayers.Renderer.SVG.LABEL_ALIGN
36602  * {Object}
36603  */
36604 OpenLayers.Renderer.SVG.LABEL_ALIGN = {
36605     "l": "start",
36606     "r": "end",
36607     "b": "bottom",
36608     "t": "hanging"
36609 };
36610
36611 /**
36612  * Constant: OpenLayers.Renderer.SVG.LABEL_VSHIFT
36613  * {Object}
36614  */
36615 OpenLayers.Renderer.SVG.LABEL_VSHIFT = {
36616     // according to
36617     // http://www.w3.org/Graphics/SVG/Test/20061213/htmlObjectHarness/full-text-align-02-b.html
36618     // a baseline-shift of -70% shifts the text exactly from the
36619     // bottom to the top of the baseline, so -35% moves the text to
36620     // the center of the baseline.
36621     "t": "-70%",
36622     "b": "0"    
36623 };
36624
36625 /**
36626  * Constant: OpenLayers.Renderer.SVG.LABEL_VFACTOR
36627  * {Object}
36628  */
36629 OpenLayers.Renderer.SVG.LABEL_VFACTOR = {
36630     "t": 0,
36631     "b": -1
36632 };
36633
36634 /**
36635  * Function: OpenLayers.Renderer.SVG.preventDefault
36636  * *Deprecated*.  Use <OpenLayers.Event.preventDefault> method instead.
36637  * Used to prevent default events (especially opening images in a new tab on
36638  * ctrl-click) from being executed for externalGraphic symbols
36639  */
36640 OpenLayers.Renderer.SVG.preventDefault = function(e) {
36641     OpenLayers.Event.preventDefault(e);
36642 };
36643 /* ======================================================================
36644     OpenLayers/Renderer/VML.js
36645    ====================================================================== */
36646
36647 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
36648  * full list of contributors). Published under the 2-clause BSD license.
36649  * See license.txt in the OpenLayers distribution or repository for the
36650  * full text of the license. */
36651
36652 /**
36653  * @requires OpenLayers/Renderer/Elements.js
36654  */
36655
36656 /**
36657  * Class: OpenLayers.Renderer.VML
36658  * Render vector features in browsers with VML capability.  Construct a new
36659  * VML renderer with the <OpenLayers.Renderer.VML> constructor.
36660  * 
36661  * Note that for all calculations in this class, we use (num | 0) to truncate a 
36662  * float value to an integer. This is done because it seems that VML doesn't 
36663  * support float values.
36664  *
36665  * Inherits from:
36666  *  - <OpenLayers.Renderer.Elements>
36667  */
36668 OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, {
36669
36670     /**
36671      * Property: xmlns
36672      * {String} XML Namespace URN
36673      */
36674     xmlns: "urn:schemas-microsoft-com:vml",
36675     
36676     /**
36677      * Property: symbolCache
36678      * {DOMElement} node holding symbols. This hash is keyed by symbol name,
36679      *     and each value is a hash with a "path" and an "extent" property.
36680      */
36681     symbolCache: {},
36682
36683     /**
36684      * Property: offset
36685      * {Object} Hash with "x" and "y" properties
36686      */
36687     offset: null,
36688     
36689     /**
36690      * Constructor: OpenLayers.Renderer.VML
36691      * Create a new VML renderer.
36692      *
36693      * Parameters:
36694      * containerID - {String} The id for the element that contains the renderer
36695      */
36696     initialize: function(containerID) {
36697         if (!this.supported()) { 
36698             return; 
36699         }
36700         if (!document.namespaces.olv) {
36701             document.namespaces.add("olv", this.xmlns);
36702             var style = document.createStyleSheet();
36703             var shapes = ['shape','rect', 'oval', 'fill', 'stroke', 'imagedata', 'group','textbox']; 
36704             for (var i = 0, len = shapes.length; i < len; i++) {
36705
36706                 style.addRule('olv\\:' + shapes[i], "behavior: url(#default#VML); " +
36707                               "position: absolute; display: inline-block;");
36708             }                  
36709         }
36710         
36711         OpenLayers.Renderer.Elements.prototype.initialize.apply(this, 
36712                                                                 arguments);
36713     },
36714
36715     /**
36716      * APIMethod: supported
36717      * Determine whether a browser supports this renderer.
36718      *
36719      * Returns:
36720      * {Boolean} The browser supports the VML renderer
36721      */
36722     supported: function() {
36723         return !!(document.namespaces);
36724     },    
36725
36726     /**
36727      * Method: setExtent
36728      * Set the renderer's extent
36729      *
36730      * Parameters:
36731      * extent - {<OpenLayers.Bounds>}
36732      * resolutionChanged - {Boolean}
36733      * 
36734      * Returns:
36735      * {Boolean} true to notify the layer that the new extent does not exceed
36736      *     the coordinate range, and the features will not need to be redrawn.
36737      */
36738     setExtent: function(extent, resolutionChanged) {
36739         var coordSysUnchanged = OpenLayers.Renderer.Elements.prototype.setExtent.apply(this, arguments);
36740         var resolution = this.getResolution();
36741     
36742         var left = (extent.left/resolution) | 0;
36743         var top = (extent.top/resolution - this.size.h) | 0;
36744         if (resolutionChanged || !this.offset) {
36745             this.offset = {x: left, y: top};
36746             left = 0;
36747             top = 0;
36748         } else {
36749             left = left - this.offset.x;
36750             top = top - this.offset.y;
36751         }
36752
36753         
36754         var org = (left - this.xOffset) + " " + top;
36755         this.root.coordorigin = org;
36756         var roots = [this.root, this.vectorRoot, this.textRoot];
36757         var root;
36758         for(var i=0, len=roots.length; i<len; ++i) {
36759             root = roots[i];
36760
36761             var size = this.size.w + " " + this.size.h;
36762             root.coordsize = size;
36763             
36764         }
36765         // flip the VML display Y axis upside down so it 
36766         // matches the display Y axis of the map
36767         this.root.style.flip = "y";
36768         
36769         return coordSysUnchanged;
36770     },
36771
36772
36773     /**
36774      * Method: setSize
36775      * Set the size of the drawing surface
36776      *
36777      * Parameters:
36778      * size - {<OpenLayers.Size>} the size of the drawing surface
36779      */
36780     setSize: function(size) {
36781         OpenLayers.Renderer.prototype.setSize.apply(this, arguments);
36782         
36783         // setting width and height on all roots to avoid flicker which we
36784         // would get with 100% width and height on child roots
36785         var roots = [
36786             this.rendererRoot,
36787             this.root,
36788             this.vectorRoot,
36789             this.textRoot
36790         ];
36791         var w = this.size.w + "px";
36792         var h = this.size.h + "px";
36793         var root;
36794         for(var i=0, len=roots.length; i<len; ++i) {
36795             root = roots[i];
36796             root.style.width = w;
36797             root.style.height = h;
36798         }
36799     },
36800
36801     /**
36802      * Method: getNodeType
36803      * Get the node type for a geometry and style
36804      *
36805      * Parameters:
36806      * geometry - {<OpenLayers.Geometry>}
36807      * style - {Object}
36808      *
36809      * Returns:
36810      * {String} The corresponding node type for the specified geometry
36811      */
36812     getNodeType: function(geometry, style) {
36813         var nodeType = null;
36814         switch (geometry.CLASS_NAME) {
36815             case "OpenLayers.Geometry.Point":
36816                 if (style.externalGraphic) {
36817                     nodeType = "olv:rect";
36818                 } else if (this.isComplexSymbol(style.graphicName)) {
36819                     nodeType = "olv:shape";
36820                 } else {
36821                     nodeType = "olv:oval";
36822                 }
36823                 break;
36824             case "OpenLayers.Geometry.Rectangle":
36825                 nodeType = "olv:rect";
36826                 break;
36827             case "OpenLayers.Geometry.LineString":
36828             case "OpenLayers.Geometry.LinearRing":
36829             case "OpenLayers.Geometry.Polygon":
36830             case "OpenLayers.Geometry.Curve":
36831                 nodeType = "olv:shape";
36832                 break;
36833             default:
36834                 break;
36835         }
36836         return nodeType;
36837     },
36838
36839     /**
36840      * Method: setStyle
36841      * Use to set all the style attributes to a VML node.
36842      *
36843      * Parameters:
36844      * node - {DOMElement} An VML element to decorate
36845      * style - {Object}
36846      * options - {Object} Currently supported options include 
36847      *                              'isFilled' {Boolean} and
36848      *                              'isStroked' {Boolean}
36849      * geometry - {<OpenLayers.Geometry>}
36850      */
36851     setStyle: function(node, style, options, geometry) {
36852         style = style  || node._style;
36853         options = options || node._options;
36854         var fillColor = style.fillColor;
36855
36856         var title = style.title || style.graphicTitle;
36857         if (title) {
36858             node.title = title;
36859         } 
36860
36861         if (node._geometryClass === "OpenLayers.Geometry.Point") {
36862             if (style.externalGraphic) {
36863                 options.isFilled = true;
36864                 var width = style.graphicWidth || style.graphicHeight;
36865                 var height = style.graphicHeight || style.graphicWidth;
36866                 width = width ? width : style.pointRadius*2;
36867                 height = height ? height : style.pointRadius*2;
36868
36869                 var resolution = this.getResolution();
36870                 var xOffset = (style.graphicXOffset != undefined) ?
36871                     style.graphicXOffset : -(0.5 * width);
36872                 var yOffset = (style.graphicYOffset != undefined) ?
36873                     style.graphicYOffset : -(0.5 * height);
36874                 
36875                 node.style.left = ((((geometry.x - this.featureDx)/resolution - this.offset.x)+xOffset) | 0) + "px";
36876                 node.style.top = (((geometry.y/resolution - this.offset.y)-(yOffset+height)) | 0) + "px";
36877                 node.style.width = width + "px";
36878                 node.style.height = height + "px";
36879                 node.style.flip = "y";
36880                 
36881                 // modify fillColor and options for stroke styling below
36882                 fillColor = "none";
36883                 options.isStroked = false;
36884             } else if (this.isComplexSymbol(style.graphicName)) {
36885                 var cache = this.importSymbol(style.graphicName);
36886                 node.path = cache.path;
36887                 node.coordorigin = cache.left + "," + cache.bottom;
36888                 var size = cache.size;
36889                 node.coordsize = size + "," + size;        
36890                 this.drawCircle(node, geometry, style.pointRadius);
36891                 node.style.flip = "y";
36892             } else {
36893                 this.drawCircle(node, geometry, style.pointRadius);
36894             }
36895         }
36896
36897         // fill 
36898         if (options.isFilled) { 
36899             node.fillcolor = fillColor; 
36900         } else { 
36901             node.filled = "false"; 
36902         }
36903         var fills = node.getElementsByTagName("fill");
36904         var fill = (fills.length == 0) ? null : fills[0];
36905         if (!options.isFilled) {
36906             if (fill) {
36907                 node.removeChild(fill);
36908             }
36909         } else {
36910             if (!fill) {
36911                 fill = this.createNode('olv:fill', node.id + "_fill");
36912             }
36913             fill.opacity = style.fillOpacity;
36914
36915             if (node._geometryClass === "OpenLayers.Geometry.Point" &&
36916                     style.externalGraphic) {
36917
36918                 // override fillOpacity
36919                 if (style.graphicOpacity) {
36920                     fill.opacity = style.graphicOpacity;
36921                 }
36922                 
36923                 fill.src = style.externalGraphic;
36924                 fill.type = "frame";
36925                 
36926                 if (!(style.graphicWidth && style.graphicHeight)) {
36927                   fill.aspect = "atmost";
36928                 }                
36929             }
36930             if (fill.parentNode != node) {
36931                 node.appendChild(fill);
36932             }
36933         }
36934
36935         // additional rendering for rotated graphics or symbols
36936         var rotation = style.rotation;
36937         if ((rotation !== undefined || node._rotation !== undefined)) {
36938             node._rotation = rotation;
36939             if (style.externalGraphic) {
36940                 this.graphicRotate(node, xOffset, yOffset, style);
36941                 // make the fill fully transparent, because we now have
36942                 // the graphic as imagedata element. We cannot just remove
36943                 // the fill, because this is part of the hack described
36944                 // in graphicRotate
36945                 fill.opacity = 0;
36946             } else if(node._geometryClass === "OpenLayers.Geometry.Point") {
36947                 node.style.rotation = rotation || 0;
36948             }
36949         }
36950
36951         // stroke 
36952         var strokes = node.getElementsByTagName("stroke");
36953         var stroke = (strokes.length == 0) ? null : strokes[0];
36954         if (!options.isStroked) {
36955             node.stroked = false;
36956             if (stroke) {
36957                 stroke.on = false;
36958             }
36959         } else {
36960             if (!stroke) {
36961                 stroke = this.createNode('olv:stroke', node.id + "_stroke");
36962                 node.appendChild(stroke);
36963             }
36964             stroke.on = true;
36965             stroke.color = style.strokeColor; 
36966             stroke.weight = style.strokeWidth + "px"; 
36967             stroke.opacity = style.strokeOpacity;
36968             stroke.endcap = style.strokeLinecap == 'butt' ? 'flat' :
36969                 (style.strokeLinecap || 'round');
36970             if (style.strokeDashstyle) {
36971                 stroke.dashstyle = this.dashStyle(style);
36972             }
36973         }
36974         
36975         if (style.cursor != "inherit" && style.cursor != null) {
36976             node.style.cursor = style.cursor;
36977         }
36978         return node;
36979     },
36980
36981     /**
36982      * Method: graphicRotate
36983      * If a point is to be styled with externalGraphic and rotation, VML fills
36984      * cannot be used to display the graphic, because rotation of graphic
36985      * fills is not supported by the VML implementation of Internet Explorer.
36986      * This method creates a olv:imagedata element inside the VML node,
36987      * DXImageTransform.Matrix and BasicImage filters for rotation and
36988      * opacity, and a 3-step hack to remove rendering artefacts from the
36989      * graphic and preserve the ability of graphics to trigger events.
36990      * Finally, OpenLayers methods are used to determine the correct
36991      * insertion point of the rotated image, because DXImageTransform.Matrix
36992      * does the rotation without the ability to specify a rotation center
36993      * point.
36994      * 
36995      * Parameters:
36996      * node    - {DOMElement}
36997      * xOffset - {Number} rotation center relative to image, x coordinate
36998      * yOffset - {Number} rotation center relative to image, y coordinate
36999      * style   - {Object}
37000      */
37001     graphicRotate: function(node, xOffset, yOffset, style) {
37002         var style = style || node._style;
37003         var rotation = style.rotation || 0;
37004         
37005         var aspectRatio, size;
37006         if (!(style.graphicWidth && style.graphicHeight)) {
37007             // load the image to determine its size
37008             var img = new Image();
37009             img.onreadystatechange = OpenLayers.Function.bind(function() {
37010                 if(img.readyState == "complete" ||
37011                         img.readyState == "interactive") {
37012                     aspectRatio = img.width / img.height;
37013                     size = Math.max(style.pointRadius * 2, 
37014                         style.graphicWidth || 0,
37015                         style.graphicHeight || 0);
37016                     xOffset = xOffset * aspectRatio;
37017                     style.graphicWidth = size * aspectRatio;
37018                     style.graphicHeight = size;
37019                     this.graphicRotate(node, xOffset, yOffset, style);
37020                 }
37021             }, this);
37022             img.src = style.externalGraphic;
37023             
37024             // will be called again by the onreadystate handler
37025             return;
37026         } else {
37027             size = Math.max(style.graphicWidth, style.graphicHeight);
37028             aspectRatio = style.graphicWidth / style.graphicHeight;
37029         }
37030         
37031         var width = Math.round(style.graphicWidth || size * aspectRatio);
37032         var height = Math.round(style.graphicHeight || size);
37033         node.style.width = width + "px";
37034         node.style.height = height + "px";
37035         
37036         // Three steps are required to remove artefacts for images with
37037         // transparent backgrounds (resulting from using DXImageTransform
37038         // filters on svg objects), while preserving awareness for browser
37039         // events on images:
37040         // - Use the fill as usual (like for unrotated images) to handle
37041         //   events
37042         // - specify an imagedata element with the same src as the fill
37043         // - style the imagedata element with an AlphaImageLoader filter
37044         //   with empty src
37045         var image = document.getElementById(node.id + "_image");
37046         if (!image) {
37047             image = this.createNode("olv:imagedata", node.id + "_image");
37048             node.appendChild(image);
37049         }
37050         image.style.width = width + "px";
37051         image.style.height = height + "px";
37052         image.src = style.externalGraphic;
37053         image.style.filter =
37054             "progid:DXImageTransform.Microsoft.AlphaImageLoader(" + 
37055             "src='', sizingMethod='scale')";
37056
37057         var rot = rotation * Math.PI / 180;
37058         var sintheta = Math.sin(rot);
37059         var costheta = Math.cos(rot);
37060
37061         // do the rotation on the image
37062         var filter =
37063             "progid:DXImageTransform.Microsoft.Matrix(M11=" + costheta +
37064             ",M12=" + (-sintheta) + ",M21=" + sintheta + ",M22=" + costheta +
37065             ",SizingMethod='auto expand')\n";
37066
37067         // set the opacity (needed for the imagedata)
37068         var opacity = style.graphicOpacity || style.fillOpacity;
37069         if (opacity && opacity != 1) {
37070             filter += 
37071                 "progid:DXImageTransform.Microsoft.BasicImage(opacity=" + 
37072                 opacity+")\n";
37073         }
37074         node.style.filter = filter;
37075
37076         // do the rotation again on a box, so we know the insertion point
37077         var centerPoint = new OpenLayers.Geometry.Point(-xOffset, -yOffset);
37078         var imgBox = new OpenLayers.Bounds(0, 0, width, height).toGeometry();
37079         imgBox.rotate(style.rotation, centerPoint);
37080         var imgBounds = imgBox.getBounds();
37081
37082         node.style.left = Math.round(
37083             parseInt(node.style.left) + imgBounds.left) + "px";
37084         node.style.top = Math.round(
37085             parseInt(node.style.top) - imgBounds.bottom) + "px";
37086     },
37087
37088     /**
37089      * Method: postDraw
37090      * Does some node postprocessing to work around browser issues:
37091      * - Some versions of Internet Explorer seem to be unable to set fillcolor
37092      *   and strokecolor to "none" correctly before the fill node is appended
37093      *   to a visible vml node. This method takes care of that and sets
37094      *   fillcolor and strokecolor again if needed.
37095      * - In some cases, a node won't become visible after being drawn. Setting
37096      *   style.visibility to "visible" works around that.
37097      * 
37098      * Parameters:
37099      * node - {DOMElement}
37100      */
37101     postDraw: function(node) {
37102         node.style.visibility = "visible";
37103         var fillColor = node._style.fillColor;
37104         var strokeColor = node._style.strokeColor;
37105         if (fillColor == "none" &&
37106                 node.fillcolor != fillColor) {
37107             node.fillcolor = fillColor;
37108         }
37109         if (strokeColor == "none" &&
37110                 node.strokecolor != strokeColor) {
37111             node.strokecolor = strokeColor;
37112         }
37113     },
37114
37115
37116     /**
37117      * Method: setNodeDimension
37118      * Get the geometry's bounds, convert it to our vml coordinate system, 
37119      * then set the node's position, size, and local coordinate system.
37120      *   
37121      * Parameters:
37122      * node - {DOMElement}
37123      * geometry - {<OpenLayers.Geometry>}
37124      */
37125     setNodeDimension: function(node, geometry) {
37126
37127         var bbox = geometry.getBounds();
37128         if(bbox) {
37129             var resolution = this.getResolution();
37130         
37131             var scaledBox = 
37132                 new OpenLayers.Bounds(((bbox.left - this.featureDx)/resolution - this.offset.x) | 0,
37133                                       (bbox.bottom/resolution - this.offset.y) | 0,
37134                                       ((bbox.right - this.featureDx)/resolution - this.offset.x) | 0,
37135                                       (bbox.top/resolution - this.offset.y) | 0);
37136             
37137             // Set the internal coordinate system to draw the path
37138             node.style.left = scaledBox.left + "px";
37139             node.style.top = scaledBox.top + "px";
37140             node.style.width = scaledBox.getWidth() + "px";
37141             node.style.height = scaledBox.getHeight() + "px";
37142     
37143             node.coordorigin = scaledBox.left + " " + scaledBox.top;
37144             node.coordsize = scaledBox.getWidth()+ " " + scaledBox.getHeight();
37145         }
37146     },
37147     
37148     /** 
37149      * Method: dashStyle
37150      * 
37151      * Parameters:
37152      * style - {Object}
37153      * 
37154      * Returns:
37155      * {String} A VML compliant 'stroke-dasharray' value
37156      */
37157     dashStyle: function(style) {
37158         var dash = style.strokeDashstyle;
37159         switch (dash) {
37160             case 'solid':
37161             case 'dot':
37162             case 'dash':
37163             case 'dashdot':
37164             case 'longdash':
37165             case 'longdashdot':
37166                 return dash;
37167             default:
37168                 // very basic guessing of dash style patterns
37169                 var parts = dash.split(/[ ,]/);
37170                 if (parts.length == 2) {
37171                     if (1*parts[0] >= 2*parts[1]) {
37172                         return "longdash";
37173                     }
37174                     return (parts[0] == 1 || parts[1] == 1) ? "dot" : "dash";
37175                 } else if (parts.length == 4) {
37176                     return (1*parts[0] >= 2*parts[1]) ? "longdashdot" :
37177                         "dashdot";
37178                 }
37179                 return "solid";
37180         }
37181     },
37182
37183     /**
37184      * Method: createNode
37185      * Create a new node
37186      *
37187      * Parameters:
37188      * type - {String} Kind of node to draw
37189      * id - {String} Id for node
37190      *
37191      * Returns:
37192      * {DOMElement} A new node of the given type and id
37193      */
37194     createNode: function(type, id) {
37195         var node = document.createElement(type);
37196         if (id) {
37197             node.id = id;
37198         }
37199         
37200         // IE hack to make elements unselectable, to prevent 'blue flash'
37201         // while dragging vectors; #1410
37202         node.unselectable = 'on';
37203         node.onselectstart = OpenLayers.Function.False;
37204         
37205         return node;    
37206     },
37207     
37208     /**
37209      * Method: nodeTypeCompare
37210      * Determine whether a node is of a given type
37211      *
37212      * Parameters:
37213      * node - {DOMElement} An VML element
37214      * type - {String} Kind of node
37215      *
37216      * Returns:
37217      * {Boolean} Whether or not the specified node is of the specified type
37218      */
37219     nodeTypeCompare: function(node, type) {
37220
37221         //split type
37222         var subType = type;
37223         var splitIndex = subType.indexOf(":");
37224         if (splitIndex != -1) {
37225             subType = subType.substr(splitIndex+1);
37226         }
37227
37228         //split nodeName
37229         var nodeName = node.nodeName;
37230         splitIndex = nodeName.indexOf(":");
37231         if (splitIndex != -1) {
37232             nodeName = nodeName.substr(splitIndex+1);
37233         }
37234
37235         return (subType == nodeName);
37236     },
37237
37238     /**
37239      * Method: createRenderRoot
37240      * Create the renderer root
37241      *
37242      * Returns:
37243      * {DOMElement} The specific render engine's root element
37244      */
37245     createRenderRoot: function() {
37246         return this.nodeFactory(this.container.id + "_vmlRoot", "div");
37247     },
37248
37249     /**
37250      * Method: createRoot
37251      * Create the main root element
37252      * 
37253      * Parameters:
37254      * suffix - {String} suffix to append to the id
37255      *
37256      * Returns:
37257      * {DOMElement}
37258      */
37259     createRoot: function(suffix) {
37260         return this.nodeFactory(this.container.id + suffix, "olv:group");
37261     },
37262     
37263     /**************************************
37264      *                                    *
37265      *     GEOMETRY DRAWING FUNCTIONS     *
37266      *                                    *
37267      **************************************/
37268     
37269     /**
37270      * Method: drawPoint
37271      * Render a point
37272      * 
37273      * Parameters:
37274      * node - {DOMElement}
37275      * geometry - {<OpenLayers.Geometry>}
37276      * 
37277      * Returns:
37278      * {DOMElement} or false if the point could not be drawn
37279      */
37280     drawPoint: function(node, geometry) {
37281         return this.drawCircle(node, geometry, 1);
37282     },
37283
37284     /**
37285      * Method: drawCircle
37286      * Render a circle.
37287      * Size and Center a circle given geometry (x,y center) and radius
37288      * 
37289      * Parameters:
37290      * node - {DOMElement}
37291      * geometry - {<OpenLayers.Geometry>}
37292      * radius - {float}
37293      * 
37294      * Returns:
37295      * {DOMElement} or false if the circle could not ne drawn
37296      */
37297     drawCircle: function(node, geometry, radius) {
37298         if(!isNaN(geometry.x)&& !isNaN(geometry.y)) {
37299             var resolution = this.getResolution();
37300
37301             node.style.left = ((((geometry.x - this.featureDx) /resolution - this.offset.x) | 0) - radius) + "px";
37302             node.style.top = (((geometry.y /resolution - this.offset.y) | 0) - radius) + "px";
37303     
37304             var diameter = radius * 2;
37305             
37306             node.style.width = diameter + "px";
37307             node.style.height = diameter + "px";
37308             return node;
37309         }
37310         return false;
37311     },
37312
37313
37314     /**
37315      * Method: drawLineString
37316      * Render a linestring.
37317      * 
37318      * Parameters:
37319      * node - {DOMElement}
37320      * geometry - {<OpenLayers.Geometry>}
37321      * 
37322      * Returns:
37323      * {DOMElement}
37324      */
37325     drawLineString: function(node, geometry) {
37326         return this.drawLine(node, geometry, false);
37327     },
37328
37329     /**
37330      * Method: drawLinearRing
37331      * Render a linearring
37332      * 
37333      * Parameters:
37334      * node - {DOMElement}
37335      * geometry - {<OpenLayers.Geometry>}
37336      * 
37337      * Returns:
37338      * {DOMElement}
37339      */
37340     drawLinearRing: function(node, geometry) {
37341         return this.drawLine(node, geometry, true);
37342     },
37343
37344     /**
37345      * Method: DrawLine
37346      * Render a line.
37347      * 
37348      * Parameters:
37349      * node - {DOMElement}
37350      * geometry - {<OpenLayers.Geometry>}
37351      * closeLine - {Boolean} Close the line? (make it a ring?)
37352      * 
37353      * Returns:
37354      * {DOMElement}
37355      */
37356     drawLine: function(node, geometry, closeLine) {
37357
37358         this.setNodeDimension(node, geometry);
37359
37360         var resolution = this.getResolution();
37361         var numComponents = geometry.components.length;
37362         var parts = new Array(numComponents);
37363
37364         var comp, x, y;
37365         for (var i = 0; i < numComponents; i++) {
37366             comp = geometry.components[i];
37367             x = ((comp.x - this.featureDx)/resolution - this.offset.x) | 0;
37368             y = (comp.y/resolution - this.offset.y) | 0;
37369             parts[i] = " " + x + "," + y + " l ";
37370         }
37371         var end = (closeLine) ? " x e" : " e";
37372         node.path = "m" + parts.join("") + end;
37373         return node;
37374     },
37375
37376     /**
37377      * Method: drawPolygon
37378      * Render a polygon
37379      * 
37380      * Parameters:
37381      * node - {DOMElement}
37382      * geometry - {<OpenLayers.Geometry>}
37383      * 
37384      * Returns:
37385      * {DOMElement}
37386      */
37387     drawPolygon: function(node, geometry) {
37388         this.setNodeDimension(node, geometry);
37389
37390         var resolution = this.getResolution();
37391     
37392         var path = [];
37393         var j, jj, points, area, first, second, i, ii, comp, pathComp, x, y;
37394         for (j=0, jj=geometry.components.length; j<jj; j++) {
37395             path.push("m");
37396             points = geometry.components[j].components;
37397             // we only close paths of interior rings with area
37398             area = (j === 0);
37399             first = null;
37400             second = null;
37401             for (i=0, ii=points.length; i<ii; i++) {
37402                 comp = points[i];
37403                 x = ((comp.x - this.featureDx) / resolution - this.offset.x) | 0;
37404                 y = (comp.y / resolution - this.offset.y) | 0;
37405                 pathComp = " " + x + "," + y;
37406                 path.push(pathComp);
37407                 if (i==0) {
37408                     path.push(" l");
37409                 }
37410                 if (!area) {
37411                     // IE improperly renders sub-paths that have no area.
37412                     // Instead of checking the area of every ring, we confirm
37413                     // the ring has at least three distinct points.  This does
37414                     // not catch all non-zero area cases, but it greatly improves
37415                     // interior ring digitizing and is a minor performance hit
37416                     // when rendering rings with many points.
37417                     if (!first) {
37418                         first = pathComp;
37419                     } else if (first != pathComp) {
37420                         if (!second) {
37421                             second = pathComp;
37422                         } else if (second != pathComp) {
37423                             // stop looking
37424                             area = true;
37425                         }
37426                     }
37427                 }
37428             }
37429             path.push(area ? " x " : " ");
37430         }
37431         path.push("e");
37432         node.path = path.join("");
37433         return node;
37434     },
37435
37436     /**
37437      * Method: drawRectangle
37438      * Render a rectangle
37439      * 
37440      * Parameters:
37441      * node - {DOMElement}
37442      * geometry - {<OpenLayers.Geometry>}
37443      * 
37444      * Returns:
37445      * {DOMElement}
37446      */
37447     drawRectangle: function(node, geometry) {
37448         var resolution = this.getResolution();
37449     
37450         node.style.left = (((geometry.x - this.featureDx)/resolution - this.offset.x) | 0) + "px";
37451         node.style.top = ((geometry.y/resolution - this.offset.y) | 0) + "px";
37452         node.style.width = ((geometry.width/resolution) | 0) + "px";
37453         node.style.height = ((geometry.height/resolution) | 0) + "px";
37454         
37455         return node;
37456     },
37457     
37458     /**
37459      * Method: drawText
37460      * This method is only called by the renderer itself.
37461      * 
37462      * Parameters: 
37463      * featureId - {String}
37464      * style -
37465      * location - {<OpenLayers.Geometry.Point>}
37466      */
37467     drawText: function(featureId, style, location) {
37468         var label = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX, "olv:rect");
37469         var textbox = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX + "_textbox", "olv:textbox");
37470         
37471         var resolution = this.getResolution();
37472         label.style.left = (((location.x - this.featureDx)/resolution - this.offset.x) | 0) + "px";
37473         label.style.top = ((location.y/resolution - this.offset.y) | 0) + "px";
37474         label.style.flip = "y";
37475
37476         textbox.innerText = style.label;
37477
37478         if (style.cursor != "inherit" && style.cursor != null) {
37479             textbox.style.cursor = style.cursor;
37480         }
37481         if (style.fontColor) {
37482             textbox.style.color = style.fontColor;
37483         }
37484         if (style.fontOpacity) {
37485             textbox.style.filter = 'alpha(opacity=' + (style.fontOpacity * 100) + ')';
37486         }
37487         if (style.fontFamily) {
37488             textbox.style.fontFamily = style.fontFamily;
37489         }
37490         if (style.fontSize) {
37491             textbox.style.fontSize = style.fontSize;
37492         }
37493         if (style.fontWeight) {
37494             textbox.style.fontWeight = style.fontWeight;
37495         }
37496         if (style.fontStyle) {
37497             textbox.style.fontStyle = style.fontStyle;
37498         }
37499         if(style.labelSelect === true) {
37500             label._featureId = featureId;
37501             textbox._featureId = featureId;
37502             textbox._geometry = location;
37503             textbox._geometryClass = location.CLASS_NAME;
37504         }
37505         textbox.style.whiteSpace = "nowrap";
37506         // fun with IE: IE7 in standards compliant mode does not display any
37507         // text with a left inset of 0. So we set this to 1px and subtract one
37508         // pixel later when we set label.style.left
37509         textbox.inset = "1px,0px,0px,0px";
37510
37511         if(!label.parentNode) {
37512             label.appendChild(textbox);
37513             this.textRoot.appendChild(label);
37514         }
37515
37516         var align = style.labelAlign || "cm";
37517         if (align.length == 1) {
37518             align += "m";
37519         }
37520         var xshift = textbox.clientWidth *
37521             (OpenLayers.Renderer.VML.LABEL_SHIFT[align.substr(0,1)]);
37522         var yshift = textbox.clientHeight *
37523             (OpenLayers.Renderer.VML.LABEL_SHIFT[align.substr(1,1)]);
37524         label.style.left = parseInt(label.style.left)-xshift-1+"px";
37525         label.style.top = parseInt(label.style.top)+yshift+"px";
37526         
37527     },
37528     
37529     /**
37530      * Method: moveRoot
37531      * moves this renderer's root to a different renderer.
37532      * 
37533      * Parameters:
37534      * renderer - {<OpenLayers.Renderer>} target renderer for the moved root
37535      * root - {DOMElement} optional root node. To be used when this renderer
37536      *     holds roots from multiple layers to tell this method which one to
37537      *     detach
37538      * 
37539      * Returns:
37540      * {Boolean} true if successful, false otherwise
37541      */
37542     moveRoot: function(renderer) {
37543         var layer = this.map.getLayer(renderer.container.id);
37544         if(layer instanceof OpenLayers.Layer.Vector.RootContainer) {
37545             layer = this.map.getLayer(this.container.id);
37546         }
37547         layer && layer.renderer.clear();
37548         OpenLayers.Renderer.Elements.prototype.moveRoot.apply(this, arguments);
37549         layer && layer.redraw();
37550     },
37551     
37552     /**
37553      * Method: importSymbol
37554      * add a new symbol definition from the rendererer's symbol hash
37555      * 
37556      * Parameters:
37557      * graphicName - {String} name of the symbol to import
37558      * 
37559      * Returns:
37560      * {Object} - hash of {DOMElement} "symbol" and {Number} "size"
37561      */      
37562     importSymbol: function (graphicName)  {
37563         var id = this.container.id + "-" + graphicName;
37564         
37565         // check if symbol already exists in the cache
37566         var cache = this.symbolCache[id];
37567         if (cache) {
37568             return cache;
37569         }
37570         
37571         var symbol = OpenLayers.Renderer.symbol[graphicName];
37572         if (!symbol) {
37573             throw new Error(graphicName + ' is not a valid symbol name');
37574         }
37575
37576         var symbolExtent = new OpenLayers.Bounds(
37577                                     Number.MAX_VALUE, Number.MAX_VALUE, 0, 0);
37578         
37579         var pathitems = ["m"];
37580         for (var i=0; i<symbol.length; i=i+2) {
37581             var x = symbol[i];
37582             var y = symbol[i+1];
37583             symbolExtent.left = Math.min(symbolExtent.left, x);
37584             symbolExtent.bottom = Math.min(symbolExtent.bottom, y);
37585             symbolExtent.right = Math.max(symbolExtent.right, x);
37586             symbolExtent.top = Math.max(symbolExtent.top, y);
37587
37588             pathitems.push(x);
37589             pathitems.push(y);
37590             if (i == 0) {
37591                 pathitems.push("l");
37592             }
37593         }
37594         pathitems.push("x e");
37595         var path = pathitems.join(" ");
37596
37597         var diff = (symbolExtent.getWidth() - symbolExtent.getHeight()) / 2;
37598         if(diff > 0) {
37599             symbolExtent.bottom = symbolExtent.bottom - diff;
37600             symbolExtent.top = symbolExtent.top + diff;
37601         } else {
37602             symbolExtent.left = symbolExtent.left + diff;
37603             symbolExtent.right = symbolExtent.right - diff;
37604         }
37605         
37606         cache = {
37607             path: path,
37608             size: symbolExtent.getWidth(), // equals getHeight() now
37609             left: symbolExtent.left,
37610             bottom: symbolExtent.bottom
37611         };
37612         this.symbolCache[id] = cache;
37613         
37614         return cache;
37615     },
37616     
37617     CLASS_NAME: "OpenLayers.Renderer.VML"
37618 });
37619
37620 /**
37621  * Constant: OpenLayers.Renderer.VML.LABEL_SHIFT
37622  * {Object}
37623  */
37624 OpenLayers.Renderer.VML.LABEL_SHIFT = {
37625     "l": 0,
37626     "c": .5,
37627     "r": 1,
37628     "t": 0,
37629     "m": .5,
37630     "b": 1
37631 };
37632 /* ======================================================================
37633     OpenLayers/Tile.js
37634    ====================================================================== */
37635
37636 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
37637  * full list of contributors). Published under the 2-clause BSD license.
37638  * See license.txt in the OpenLayers distribution or repository for the
37639  * full text of the license. */
37640
37641
37642 /**
37643  * @requires OpenLayers/BaseTypes/Class.js
37644  * @requires OpenLayers/Util.js
37645  */
37646
37647 /**
37648  * Class: OpenLayers.Tile 
37649  * This is a class designed to designate a single tile, however
37650  *     it is explicitly designed to do relatively little. Tiles store 
37651  *     information about themselves -- such as the URL that they are related
37652  *     to, and their size - but do not add themselves to the layer div 
37653  *     automatically, for example. Create a new tile with the 
37654  *     <OpenLayers.Tile> constructor, or a subclass. 
37655  * 
37656  * TBD 3.0 - remove reference to url in above paragraph
37657  * 
37658  */
37659 OpenLayers.Tile = OpenLayers.Class({
37660     
37661     /**
37662      * APIProperty: events
37663      * {<OpenLayers.Events>} An events object that handles all 
37664      *     events on the tile.
37665      *
37666      * Register a listener for a particular event with the following syntax:
37667      * (code)
37668      * tile.events.register(type, obj, listener);
37669      * (end)
37670      *
37671      * Supported event types:
37672      * beforedraw - Triggered before the tile is drawn. Used to defer
37673      *     drawing to an animation queue. To defer drawing, listeners need
37674      *     to return false, which will abort drawing. The queue handler needs
37675      *     to call <draw>(true) to actually draw the tile.
37676      * loadstart - Triggered when tile loading starts.
37677      * loadend - Triggered when tile loading ends.
37678      * loaderror - Triggered before the loadend event (i.e. when the tile is
37679      *     still hidden) if the tile could not be loaded.
37680      * reload - Triggered when an already loading tile is reloaded.
37681      * unload - Triggered before a tile is unloaded.
37682      */
37683     events: null,
37684
37685     /**
37686      * APIProperty: eventListeners
37687      * {Object} If set as an option at construction, the eventListeners
37688      *     object will be registered with <OpenLayers.Events.on>.  Object
37689      *     structure must be a listeners object as shown in the example for
37690      *     the events.on method.
37691      *
37692      * This options can be set in the ``tileOptions`` option from
37693      * <OpenLayers.Layer.Grid>. For example, to be notified of the
37694      * ``loadend`` event of each tiles:
37695      * (code)
37696      * new OpenLayers.Layer.OSM('osm', 'http://tile.openstreetmap.org/${z}/${x}/${y}.png', {
37697      *     tileOptions: {
37698      *         eventListeners: {
37699      *             'loadend': function(evt) {
37700      *                 // do something on loadend
37701      *             }
37702      *         }
37703      *     }
37704      * });
37705      * (end)
37706      */
37707     eventListeners: null,
37708
37709     /**
37710      * Property: id 
37711      * {String} null
37712      */
37713     id: null,
37714     
37715     /** 
37716      * Property: layer 
37717      * {<OpenLayers.Layer>} layer the tile is attached to 
37718      */
37719     layer: null,
37720     
37721     /**
37722      * Property: url
37723      * {String} url of the request.
37724      *
37725      * TBD 3.0 
37726      * Deprecated. The base tile class does not need an url. This should be 
37727      * handled in subclasses. Does not belong here.
37728      */
37729     url: null,
37730
37731     /** 
37732      * APIProperty: bounds 
37733      * {<OpenLayers.Bounds>} null
37734      */
37735     bounds: null,
37736     
37737     /** 
37738      * Property: size 
37739      * {<OpenLayers.Size>} null
37740      */
37741     size: null,
37742     
37743     /** 
37744      * Property: position 
37745      * {<OpenLayers.Pixel>} Top Left pixel of the tile
37746      */    
37747     position: null,
37748     
37749     /**
37750      * Property: isLoading
37751      * {Boolean} Is the tile loading?
37752      */
37753     isLoading: false,
37754     
37755     /** TBD 3.0 -- remove 'url' from the list of parameters to the constructor.
37756      *             there is no need for the base tile class to have a url.
37757      */
37758
37759     /** 
37760      * Constructor: OpenLayers.Tile
37761      * Constructor for a new <OpenLayers.Tile> instance.
37762      * 
37763      * Parameters:
37764      * layer - {<OpenLayers.Layer>} layer that the tile will go in.
37765      * position - {<OpenLayers.Pixel>}
37766      * bounds - {<OpenLayers.Bounds>}
37767      * url - {<String>}
37768      * size - {<OpenLayers.Size>}
37769      * options - {Object}
37770      */   
37771     initialize: function(layer, position, bounds, url, size, options) {
37772         this.layer = layer;
37773         this.position = position.clone();
37774         this.setBounds(bounds);
37775         this.url = url;
37776         if (size) {
37777             this.size = size.clone();
37778         }
37779
37780         //give the tile a unique id based on its BBOX.
37781         this.id = OpenLayers.Util.createUniqueID("Tile_");
37782
37783         OpenLayers.Util.extend(this, options);
37784
37785         this.events = new OpenLayers.Events(this);
37786         if (this.eventListeners instanceof Object) {
37787             this.events.on(this.eventListeners);
37788         }
37789     },
37790
37791     /**
37792      * Method: unload
37793      * Call immediately before destroying if you are listening to tile
37794      * events, so that counters are properly handled if tile is still
37795      * loading at destroy-time. Will only fire an event if the tile is
37796      * still loading.
37797      */
37798     unload: function() {
37799        if (this.isLoading) { 
37800            this.isLoading = false; 
37801            this.events.triggerEvent("unload"); 
37802        }
37803     },
37804     
37805     /** 
37806      * APIMethod: destroy
37807      * Nullify references to prevent circular references and memory leaks.
37808      */
37809     destroy:function() {
37810         this.layer  = null;
37811         this.bounds = null;
37812         this.size = null;
37813         this.position = null;
37814         
37815         if (this.eventListeners) {
37816             this.events.un(this.eventListeners);
37817         }
37818         this.events.destroy();
37819         this.eventListeners = null;
37820         this.events = null;
37821     },
37822     
37823     /**
37824      * Method: draw
37825      * Clear whatever is currently in the tile, then return whether or not 
37826      *     it should actually be re-drawn. This is an example implementation
37827      *     that can be overridden by subclasses. The minimum thing to do here
37828      *     is to call <clear> and return the result from <shouldDraw>.
37829      *
37830      * Parameters:
37831      * force - {Boolean} If true, the tile will not be cleared and no beforedraw
37832      *     event will be fired. This is used for drawing tiles asynchronously
37833      *     after drawing has been cancelled by returning false from a beforedraw
37834      *     listener.
37835      * 
37836      * Returns:
37837      * {Boolean} Whether or not the tile should actually be drawn. Returns null
37838      *     if a beforedraw listener returned false.
37839      */
37840     draw: function(force) {
37841         if (!force) {
37842             //clear tile's contents and mark as not drawn
37843             this.clear();
37844         }
37845         var draw = this.shouldDraw();
37846         if (draw && !force && this.events.triggerEvent("beforedraw") === false) {
37847             draw = null;
37848         }
37849         return draw;
37850     },
37851     
37852     /**
37853      * Method: shouldDraw
37854      * Return whether or not the tile should actually be (re-)drawn. The only
37855      * case where we *wouldn't* want to draw the tile is if the tile is outside
37856      * its layer's maxExtent
37857      * 
37858      * Returns:
37859      * {Boolean} Whether or not the tile should actually be drawn.
37860      */
37861     shouldDraw: function() {        
37862         var withinMaxExtent = false,
37863             maxExtent = this.layer.maxExtent;
37864         if (maxExtent) {
37865             var map = this.layer.map;
37866             var worldBounds = map.baseLayer.wrapDateLine && map.getMaxExtent();
37867             if (this.bounds.intersectsBounds(maxExtent, {inclusive: false, worldBounds: worldBounds})) {
37868                 withinMaxExtent = true;
37869             }
37870         }
37871         
37872         return withinMaxExtent || this.layer.displayOutsideMaxExtent;
37873     },
37874     
37875     /**
37876      * Method: setBounds
37877      * Sets the bounds on this instance
37878      *
37879      * Parameters:
37880      * bounds {<OpenLayers.Bounds>}
37881      */
37882     setBounds: function(bounds) {
37883         bounds = bounds.clone();
37884         if (this.layer.map.baseLayer.wrapDateLine) {
37885             var worldExtent = this.layer.map.getMaxExtent(),
37886                 tolerance = this.layer.map.getResolution();
37887             bounds = bounds.wrapDateLine(worldExtent, {
37888                 leftTolerance: tolerance,
37889                 rightTolerance: tolerance
37890             });
37891         }
37892         this.bounds = bounds;
37893     },
37894     
37895     /** 
37896      * Method: moveTo
37897      * Reposition the tile.
37898      *
37899      * Parameters:
37900      * bounds - {<OpenLayers.Bounds>}
37901      * position - {<OpenLayers.Pixel>}
37902      * redraw - {Boolean} Call draw method on tile after moving.
37903      *     Default is true
37904      */
37905     moveTo: function (bounds, position, redraw) {
37906         if (redraw == null) {
37907             redraw = true;
37908         }
37909
37910         this.setBounds(bounds);
37911         this.position = position.clone();
37912         if (redraw) {
37913             this.draw();
37914         }
37915     },
37916
37917     /** 
37918      * Method: clear
37919      * Clear the tile of any bounds/position-related data so that it can 
37920      *     be reused in a new location.
37921      */
37922     clear: function(draw) {
37923         // to be extended by subclasses
37924     },
37925     
37926     CLASS_NAME: "OpenLayers.Tile"
37927 });
37928 /* ======================================================================
37929     OpenLayers/Tile/Image.js
37930    ====================================================================== */
37931
37932 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
37933  * full list of contributors). Published under the 2-clause BSD license.
37934  * See license.txt in the OpenLayers distribution or repository for the
37935  * full text of the license. */
37936
37937
37938 /**
37939  * @requires OpenLayers/Tile.js
37940  * @requires OpenLayers/Animation.js
37941  * @requires OpenLayers/Util.js
37942  */
37943
37944 /**
37945  * Class: OpenLayers.Tile.Image
37946  * Instances of OpenLayers.Tile.Image are used to manage the image tiles
37947  * used by various layers.  Create a new image tile with the
37948  * <OpenLayers.Tile.Image> constructor.
37949  *
37950  * Inherits from:
37951  *  - <OpenLayers.Tile>
37952  */
37953 OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, {
37954
37955     /**
37956      * APIProperty: events
37957      * {<OpenLayers.Events>} An events object that handles all 
37958      *     events on the tile.
37959      *
37960      * Register a listener for a particular event with the following syntax:
37961      * (code)
37962      * tile.events.register(type, obj, listener);
37963      * (end)
37964      *
37965      * Supported event types (in addition to the <OpenLayers.Tile> events):
37966      * beforeload - Triggered before an image is prepared for loading, when the
37967      *     url for the image is known already. Listeners may call <setImage> on
37968      *     the tile instance. If they do so, that image will be used and no new
37969      *     one will be created.
37970      */
37971
37972     /** 
37973      * APIProperty: url
37974      * {String} The URL of the image being requested. No default. Filled in by
37975      * layer.getURL() function. May be modified by loadstart listeners.
37976      */
37977     url: null,
37978     
37979     /** 
37980      * Property: imgDiv
37981      * {HTMLImageElement} The image for this tile.
37982      */
37983     imgDiv: null,
37984     
37985     /**
37986      * Property: frame
37987      * {DOMElement} The image element is appended to the frame.  Any gutter on
37988      * the image will be hidden behind the frame. If no gutter is set,
37989      * this will be null.
37990      */ 
37991     frame: null, 
37992
37993     /** 
37994      * Property: imageReloadAttempts
37995      * {Integer} Attempts to load the image.
37996      */
37997     imageReloadAttempts: null,
37998     
37999     /**
38000      * Property: layerAlphaHack
38001      * {Boolean} True if the png alpha hack needs to be applied on the layer's div.
38002      */
38003     layerAlphaHack: null,
38004     
38005     /**
38006      * Property: asyncRequestId
38007      * {Integer} ID of an request to see if request is still valid. This is a
38008      * number which increments by 1 for each asynchronous request.
38009      */
38010     asyncRequestId: null,
38011     
38012     /**
38013      * APIProperty: maxGetUrlLength
38014      * {Number} If set, requests that would result in GET urls with more
38015      * characters than the number provided will be made using form-encoded
38016      * HTTP POST. It is good practice to avoid urls that are longer than 2048
38017      * characters.
38018      *
38019      * Caution:
38020      * Older versions of Gecko based browsers (e.g. Firefox < 3.5) and most
38021      * Opera versions do not fully support this option. On all browsers,
38022      * transition effects are not supported if POST requests are used.
38023      */
38024     maxGetUrlLength: null,
38025
38026     /**
38027      * Property: canvasContext
38028      * {CanvasRenderingContext2D} A canvas context associated with
38029      * the tile image.
38030      */
38031     canvasContext: null,
38032     
38033     /**
38034      * APIProperty: crossOriginKeyword
38035      * The value of the crossorigin keyword to use when loading images. This is
38036      * only relevant when using <getCanvasContext> for tiles from remote
38037      * origins and should be set to either 'anonymous' or 'use-credentials'
38038      * for servers that send Access-Control-Allow-Origin headers with their
38039      * tiles.
38040      */
38041     crossOriginKeyword: null,
38042
38043     /** TBD 3.0 - reorder the parameters to the init function to remove 
38044      *             URL. the getUrl() function on the layer gets called on 
38045      *             each draw(), so no need to specify it here.
38046      */
38047
38048     /** 
38049      * Constructor: OpenLayers.Tile.Image
38050      * Constructor for a new <OpenLayers.Tile.Image> instance.
38051      * 
38052      * Parameters:
38053      * layer - {<OpenLayers.Layer>} layer that the tile will go in.
38054      * position - {<OpenLayers.Pixel>}
38055      * bounds - {<OpenLayers.Bounds>}
38056      * url - {<String>} Deprecated. Remove me in 3.0.
38057      * size - {<OpenLayers.Size>}
38058      * options - {Object}
38059      */   
38060     initialize: function(layer, position, bounds, url, size, options) {
38061         OpenLayers.Tile.prototype.initialize.apply(this, arguments);
38062
38063         this.url = url; //deprecated remove me
38064         
38065         this.layerAlphaHack = this.layer.alpha && OpenLayers.Util.alphaHack();
38066
38067         if (this.maxGetUrlLength != null || this.layer.gutter || this.layerAlphaHack) {
38068             // only create frame if it's needed
38069             this.frame = document.createElement("div");
38070             this.frame.style.position = "absolute";
38071             this.frame.style.overflow = "hidden";
38072         }
38073         if (this.maxGetUrlLength != null) {
38074             OpenLayers.Util.extend(this, OpenLayers.Tile.Image.IFrame);
38075         }
38076     },
38077     
38078     /** 
38079      * APIMethod: destroy
38080      * nullify references to prevent circular references and memory leaks
38081      */
38082     destroy: function() {
38083         if (this.imgDiv)  {
38084             this.clear();
38085             this.imgDiv = null;
38086             this.frame = null;
38087         }
38088         // don't handle async requests any more
38089         this.asyncRequestId = null;
38090         OpenLayers.Tile.prototype.destroy.apply(this, arguments);
38091     },
38092     
38093     /**
38094      * Method: draw
38095      * Check that a tile should be drawn, and draw it.
38096      * 
38097      * Returns:
38098      * {Boolean} Was a tile drawn? Or null if a beforedraw listener returned
38099      *     false.
38100      */
38101     draw: function() {
38102         var shouldDraw = OpenLayers.Tile.prototype.draw.apply(this, arguments);
38103         if (shouldDraw) {
38104             // The layer's reproject option is deprecated.
38105             if (this.layer != this.layer.map.baseLayer && this.layer.reproject) {
38106                 // getBoundsFromBaseLayer is defined in deprecated.js.
38107                 this.bounds = this.getBoundsFromBaseLayer(this.position);
38108             }
38109             if (this.isLoading) {
38110                 //if we're already loading, send 'reload' instead of 'loadstart'.
38111                 this._loadEvent = "reload";
38112             } else {
38113                 this.isLoading = true;
38114                 this._loadEvent = "loadstart";
38115             }
38116             this.renderTile();
38117             this.positionTile();
38118         } else if (shouldDraw === false) {
38119             this.unload();
38120         }
38121         return shouldDraw;
38122     },
38123     
38124     /**
38125      * Method: renderTile
38126      * Internal function to actually initialize the image tile,
38127      *     position it correctly, and set its url.
38128      */
38129     renderTile: function() {
38130         if (this.layer.async) {
38131             // Asynchronous image requests call the asynchronous getURL method
38132             // on the layer to fetch an image that covers 'this.bounds'.
38133             var id = this.asyncRequestId = (this.asyncRequestId || 0) + 1;
38134             this.layer.getURLasync(this.bounds, function(url) {
38135                 if (id == this.asyncRequestId) {
38136                     this.url = url;
38137                     this.initImage();
38138                 }
38139             }, this);
38140         } else {
38141             // synchronous image requests get the url immediately.
38142             this.url = this.layer.getURL(this.bounds);
38143             this.initImage();
38144         }
38145     },
38146
38147     /**
38148      * Method: positionTile
38149      * Using the properties currenty set on the layer, position the tile correctly.
38150      * This method is used both by the async and non-async versions of the Tile.Image
38151      * code.
38152      */
38153     positionTile: function() {
38154         var style = this.getTile().style,
38155             size = this.frame ? this.size :
38156                 this.layer.getImageSize(this.bounds),
38157             ratio = 1;
38158         if (this.layer instanceof OpenLayers.Layer.Grid) {
38159             ratio = this.layer.getServerResolution() / this.layer.map.getResolution();
38160         }
38161         style.left = this.position.x + "px";
38162         style.top = this.position.y + "px";
38163         style.width = Math.round(ratio * size.w) + "px";
38164         style.height = Math.round(ratio * size.h) + "px";
38165     },
38166
38167     /** 
38168      * Method: clear
38169      * Remove the tile from the DOM, clear it of any image related data so that
38170      * it can be reused in a new location.
38171      */
38172     clear: function() {
38173         OpenLayers.Tile.prototype.clear.apply(this, arguments);
38174         var img = this.imgDiv;
38175         if (img) {
38176             var tile = this.getTile();
38177             if (tile.parentNode === this.layer.div) {
38178                 this.layer.div.removeChild(tile);
38179             }
38180             this.setImgSrc();
38181             if (this.layerAlphaHack === true) {
38182                 img.style.filter = "";
38183             }
38184             OpenLayers.Element.removeClass(img, "olImageLoadError");
38185         }
38186         this.canvasContext = null;
38187     },
38188     
38189     /**
38190      * Method: getImage
38191      * Returns or creates and returns the tile image.
38192      */
38193     getImage: function() {
38194         if (!this.imgDiv) {
38195             this.imgDiv = OpenLayers.Tile.Image.IMAGE.cloneNode(false);
38196
38197             var style = this.imgDiv.style;
38198             if (this.frame) {
38199                 var left = 0, top = 0;
38200                 if (this.layer.gutter) {
38201                     left = this.layer.gutter / this.layer.tileSize.w * 100;
38202                     top = this.layer.gutter / this.layer.tileSize.h * 100;
38203                 }
38204                 style.left = -left + "%";
38205                 style.top = -top + "%";
38206                 style.width = (2 * left + 100) + "%";
38207                 style.height = (2 * top + 100) + "%";
38208             }
38209             style.visibility = "hidden";
38210             style.opacity = 0;
38211             if (this.layer.opacity < 1) {
38212                 style.filter = 'alpha(opacity=' +
38213                                (this.layer.opacity * 100) +
38214                                ')';
38215             }
38216             style.position = "absolute";
38217             if (this.layerAlphaHack) {
38218                 // move the image out of sight
38219                 style.paddingTop = style.height;
38220                 style.height = "0";
38221                 style.width = "100%";
38222             }
38223             if (this.frame) {
38224                 this.frame.appendChild(this.imgDiv);
38225             }
38226         }
38227
38228         return this.imgDiv;
38229     },
38230     
38231     /**
38232      * APIMethod: setImage
38233      * Sets the image element for this tile. This method should only be called
38234      * from beforeload listeners.
38235      *
38236      * Parameters
38237      * img - {HTMLImageElement} The image to use for this tile.
38238      */
38239     setImage: function(img) {
38240         this.imgDiv = img;
38241     },
38242
38243     /**
38244      * Method: initImage
38245      * Creates the content for the frame on the tile.
38246      */
38247     initImage: function() {
38248         if (!this.url && !this.imgDiv) {
38249             // fast path out - if there is no tile url and no previous image
38250             this.isLoading = false;
38251             return;
38252         }
38253         this.events.triggerEvent('beforeload');
38254         this.layer.div.appendChild(this.getTile());
38255         this.events.triggerEvent(this._loadEvent);
38256         var img = this.getImage();
38257         var src = img.getAttribute('src') || '';
38258         if (this.url && OpenLayers.Util.isEquivalentUrl(src, this.url)) {
38259             this._loadTimeout = window.setTimeout(
38260                 OpenLayers.Function.bind(this.onImageLoad, this), 0
38261             );
38262         } else {
38263             this.stopLoading();
38264             if (this.crossOriginKeyword) {
38265                 img.removeAttribute("crossorigin");
38266             }
38267             OpenLayers.Event.observe(img, "load",
38268                 OpenLayers.Function.bind(this.onImageLoad, this)
38269             );
38270             OpenLayers.Event.observe(img, "error",
38271                 OpenLayers.Function.bind(this.onImageError, this)
38272             );
38273             this.imageReloadAttempts = 0;
38274             this.setImgSrc(this.url);
38275         }
38276     },
38277     
38278     /**
38279      * Method: setImgSrc
38280      * Sets the source for the tile image
38281      *
38282      * Parameters:
38283      * url - {String} or undefined to hide the image
38284      */
38285     setImgSrc: function(url) {
38286         var img = this.imgDiv;
38287         if (url) {
38288             img.style.visibility = 'hidden';
38289             img.style.opacity = 0;
38290             // don't set crossOrigin if the url is a data URL
38291             if (this.crossOriginKeyword) {
38292                 if (url.substr(0, 5) !== 'data:') {
38293                     img.setAttribute("crossorigin", this.crossOriginKeyword);
38294                 } else {
38295                     img.removeAttribute("crossorigin");
38296                 }
38297             }
38298             img.src = url;
38299         } else {
38300             // Remove reference to the image, and leave it to the browser's
38301             // caching and garbage collection.
38302             this.stopLoading();
38303             this.imgDiv = null;
38304             if (img.parentNode) {
38305                 img.parentNode.removeChild(img);
38306             }
38307         }
38308     },
38309     
38310     /**
38311      * Method: getTile
38312      * Get the tile's markup.
38313      *
38314      * Returns:
38315      * {DOMElement} The tile's markup
38316      */
38317     getTile: function() {
38318         return this.frame ? this.frame : this.getImage();
38319     },
38320
38321     /**
38322      * Method: createBackBuffer
38323      * Create a backbuffer for this tile. A backbuffer isn't exactly a clone
38324      * of the tile's markup, because we want to avoid the reloading of the
38325      * image. So we clone the frame, and steal the image from the tile.
38326      *
38327      * Returns:
38328      * {DOMElement} The markup, or undefined if the tile has no image
38329      * or if it's currently loading.
38330      */
38331     createBackBuffer: function() {
38332         if (!this.imgDiv || this.isLoading) {
38333             return;
38334         }
38335         var backBuffer;
38336         if (this.frame) {
38337             backBuffer = this.frame.cloneNode(false);
38338             backBuffer.appendChild(this.imgDiv);
38339         } else {
38340             backBuffer = this.imgDiv;
38341         }
38342         this.imgDiv = null;
38343         return backBuffer;
38344     },
38345
38346     /**
38347      * Method: onImageLoad
38348      * Handler for the image onload event
38349      */
38350     onImageLoad: function() {
38351         var img = this.imgDiv;
38352         this.stopLoading();
38353         img.style.visibility = 'inherit';
38354         img.style.opacity = this.layer.opacity;
38355         this.isLoading = false;
38356         this.canvasContext = null;
38357         this.events.triggerEvent("loadend");
38358
38359         if (this.layerAlphaHack === true) {
38360             img.style.filter =
38361                 "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" +
38362                 img.src + "', sizingMethod='scale')";
38363         }
38364     },
38365     
38366     /**
38367      * Method: onImageError
38368      * Handler for the image onerror event
38369      */
38370     onImageError: function() {
38371         var img = this.imgDiv;
38372         if (img.src != null) {
38373             this.imageReloadAttempts++;
38374             if (this.imageReloadAttempts <= OpenLayers.IMAGE_RELOAD_ATTEMPTS) {
38375                 this.setImgSrc(this.layer.getURL(this.bounds));
38376             } else {
38377                 OpenLayers.Element.addClass(img, "olImageLoadError");
38378                 this.events.triggerEvent("loaderror");
38379                 this.onImageLoad();
38380             }
38381         }
38382     },
38383     
38384     /**
38385      * Method: stopLoading
38386      * Stops a loading sequence so <onImageLoad> won't be executed.
38387      */
38388     stopLoading: function() {
38389         OpenLayers.Event.stopObservingElement(this.imgDiv);
38390         window.clearTimeout(this._loadTimeout);
38391         delete this._loadTimeout;
38392     },
38393
38394     /**
38395      * APIMethod: getCanvasContext
38396      * Returns a canvas context associated with the tile image (with
38397      * the image drawn on it).
38398      * Returns undefined if the browser does not support canvas, if
38399      * the tile has no image or if it's currently loading.
38400      *
38401      * The function returns a canvas context instance but the
38402      * underlying canvas is still available in the 'canvas' property:
38403      * (code)
38404      * var context = tile.getCanvasContext();
38405      * if (context) {
38406      *     var data = context.canvas.toDataURL('image/jpeg');
38407      * }
38408      * (end)
38409      *
38410      * Returns:
38411      * {Boolean}
38412      */
38413     getCanvasContext: function() {
38414         if (OpenLayers.CANVAS_SUPPORTED && this.imgDiv && !this.isLoading) {
38415             if (!this.canvasContext) {
38416                 var canvas = document.createElement("canvas");
38417                 canvas.width = this.size.w;
38418                 canvas.height = this.size.h;
38419                 this.canvasContext = canvas.getContext("2d");
38420                 this.canvasContext.drawImage(this.imgDiv, 0, 0);
38421             }
38422             return this.canvasContext;
38423         }
38424     },
38425
38426     CLASS_NAME: "OpenLayers.Tile.Image"
38427
38428 });
38429
38430 /** 
38431  * Constant: OpenLayers.Tile.Image.IMAGE
38432  * {HTMLImageElement} The image for a tile.
38433  */
38434 OpenLayers.Tile.Image.IMAGE = (function() {
38435     var img = new Image();
38436     img.className = "olTileImage";
38437     // avoid image gallery menu in IE6
38438     img.galleryImg = "no";
38439     return img;
38440 }());
38441
38442 /* ======================================================================
38443     OpenLayers/Layer/Image.js
38444    ====================================================================== */
38445
38446 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
38447  * full list of contributors). Published under the 2-clause BSD license.
38448  * See license.txt in the OpenLayers distribution or repository for the
38449  * full text of the license. */
38450  
38451 /**
38452  * @requires OpenLayers/Layer.js
38453  * @requires OpenLayers/Tile/Image.js
38454  */
38455
38456 /**
38457  * Class: OpenLayers.Layer.Image
38458  * Instances of OpenLayers.Layer.Image are used to display data from a web
38459  * accessible image as a map layer.  Create a new image layer with the
38460  * <OpenLayers.Layer.Image> constructor.
38461  *
38462  * Inherits from:
38463  *  - <OpenLayers.Layer>
38464  */
38465 OpenLayers.Layer.Image = OpenLayers.Class(OpenLayers.Layer, {
38466
38467     /**
38468      * Property: isBaseLayer
38469      * {Boolean} The layer is a base layer.  Default is true.  Set this property
38470      * in the layer options
38471      */
38472     isBaseLayer: true,
38473     
38474     /**
38475      * Property: url
38476      * {String} URL of the image to use
38477      */
38478     url: null,
38479
38480     /**
38481      * Property: extent
38482      * {<OpenLayers.Bounds>} The image bounds in map units.  This extent will
38483      *     also be used as the default maxExtent for the layer.  If you wish
38484      *     to have a maxExtent that is different than the image extent, set the
38485      *     maxExtent property of the options argument (as with any other layer).
38486      */
38487     extent: null,
38488     
38489     /**
38490      * Property: size
38491      * {<OpenLayers.Size>} The image size in pixels
38492      */
38493     size: null,
38494
38495     /**
38496      * Property: tile
38497      * {<OpenLayers.Tile.Image>}
38498      */
38499     tile: null,
38500
38501     /**
38502      * Property: aspectRatio
38503      * {Float} The ratio of height/width represented by a single pixel in the
38504      * graphic
38505      */
38506     aspectRatio: null,
38507
38508     /**
38509      * Constructor: OpenLayers.Layer.Image
38510      * Create a new image layer
38511      *
38512      * Parameters:
38513      * name - {String} A name for the layer.
38514      * url - {String} Relative or absolute path to the image
38515      * extent - {<OpenLayers.Bounds>} The extent represented by the image
38516      * size - {<OpenLayers.Size>} The size (in pixels) of the image
38517      * options - {Object} Hashtable of extra options to tag onto the layer
38518      */
38519     initialize: function(name, url, extent, size, options) {
38520         this.url = url;
38521         this.extent = extent;
38522         this.maxExtent = extent;
38523         this.size = size;
38524         OpenLayers.Layer.prototype.initialize.apply(this, [name, options]);
38525
38526         this.aspectRatio = (this.extent.getHeight() / this.size.h) /
38527                            (this.extent.getWidth() / this.size.w);
38528     },    
38529
38530     /**
38531      * Method: destroy
38532      * Destroy this layer
38533      */
38534     destroy: function() {
38535         if (this.tile) {
38536             this.removeTileMonitoringHooks(this.tile);
38537             this.tile.destroy();
38538             this.tile = null;
38539         }
38540         OpenLayers.Layer.prototype.destroy.apply(this, arguments);
38541     },
38542     
38543     /**
38544      * Method: clone
38545      * Create a clone of this layer
38546      *
38547      * Parameters:
38548      * obj - {Object} An optional layer (is this ever used?)
38549      *
38550      * Returns:
38551      * {<OpenLayers.Layer.Image>} An exact copy of this layer
38552      */
38553     clone: function(obj) {
38554         
38555         if(obj == null) {
38556             obj = new OpenLayers.Layer.Image(this.name,
38557                                                this.url,
38558                                                this.extent,
38559                                                this.size,
38560                                                this.getOptions());
38561         }
38562
38563         //get all additions from superclasses
38564         obj = OpenLayers.Layer.prototype.clone.apply(this, [obj]);
38565
38566         // copy/set any non-init, non-simple values here
38567
38568         return obj;
38569     },    
38570     
38571     /**
38572      * APIMethod: setMap
38573      * 
38574      * Parameters:
38575      * map - {<OpenLayers.Map>}
38576      */
38577     setMap: function(map) {
38578         /**
38579          * If nothing to do with resolutions has been set, assume a single
38580          * resolution determined by ratio*extent/size - if an image has a
38581          * pixel aspect ratio different than one (as calculated above), the
38582          * image will be stretched in one dimension only.
38583          */
38584         if( this.options.maxResolution == null ) {
38585             this.options.maxResolution = this.aspectRatio *
38586                                          this.extent.getWidth() /
38587                                          this.size.w;
38588         }
38589         OpenLayers.Layer.prototype.setMap.apply(this, arguments);
38590     },
38591
38592     /** 
38593      * Method: moveTo
38594      * Create the tile for the image or resize it for the new resolution
38595      * 
38596      * Parameters:
38597      * bounds - {<OpenLayers.Bounds>}
38598      * zoomChanged - {Boolean}
38599      * dragging - {Boolean}
38600      */
38601     moveTo:function(bounds, zoomChanged, dragging) {
38602         OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
38603
38604         var firstRendering = (this.tile == null);
38605
38606         if(zoomChanged || firstRendering) {
38607
38608             //determine new tile size
38609             this.setTileSize();
38610
38611             //determine new position (upper left corner of new bounds)
38612             var ulPx = this.map.getLayerPxFromLonLat({
38613                 lon: this.extent.left,
38614                 lat: this.extent.top
38615             });
38616
38617             if(firstRendering) {
38618                 //create the new tile
38619                 this.tile = new OpenLayers.Tile.Image(this, ulPx, this.extent, 
38620                                                       null, this.tileSize);
38621                 this.addTileMonitoringHooks(this.tile);
38622             } else {
38623                 //just resize the tile and set it's new position
38624                 this.tile.size = this.tileSize.clone();
38625                 this.tile.position = ulPx.clone();
38626             }
38627             this.tile.draw();
38628         }
38629     }, 
38630
38631     /**
38632      * Set the tile size based on the map size.
38633      */
38634     setTileSize: function() {
38635         var tileWidth = this.extent.getWidth() / this.map.getResolution();
38636         var tileHeight = this.extent.getHeight() / this.map.getResolution();
38637         this.tileSize = new OpenLayers.Size(tileWidth, tileHeight);
38638     },
38639
38640     /** 
38641      * Method: addTileMonitoringHooks
38642      * This function takes a tile as input and adds the appropriate hooks to 
38643      *     the tile so that the layer can keep track of the loading tiles.
38644      * 
38645      * Parameters: 
38646      * tile - {<OpenLayers.Tile>}
38647      */
38648     addTileMonitoringHooks: function(tile) {
38649         tile.onLoadStart = function() {
38650             this.events.triggerEvent("loadstart");
38651         };
38652         tile.events.register("loadstart", this, tile.onLoadStart);
38653       
38654         tile.onLoadEnd = function() {
38655             this.events.triggerEvent("loadend");
38656         };
38657         tile.events.register("loadend", this, tile.onLoadEnd);
38658         tile.events.register("unload", this, tile.onLoadEnd);
38659     },
38660
38661     /** 
38662      * Method: removeTileMonitoringHooks
38663      * This function takes a tile as input and removes the tile hooks 
38664      *     that were added in <addTileMonitoringHooks>.
38665      * 
38666      * Parameters: 
38667      * tile - {<OpenLayers.Tile>}
38668      */
38669     removeTileMonitoringHooks: function(tile) {
38670         tile.unload();
38671         tile.events.un({
38672             "loadstart": tile.onLoadStart,
38673             "loadend": tile.onLoadEnd,
38674             "unload": tile.onLoadEnd,
38675             scope: this
38676         });
38677     },
38678     
38679     /**
38680      * APIMethod: setUrl
38681      * 
38682      * Parameters:
38683      * newUrl - {String}
38684      */
38685     setUrl: function(newUrl) {
38686         this.url = newUrl;
38687         this.tile.draw();
38688     },
38689
38690     /** 
38691      * APIMethod: getURL
38692      * The url we return is always the same (the image itself never changes)
38693      *     so we can ignore the bounds parameter (it will always be the same, 
38694      *     anyways) 
38695      * 
38696      * Parameters:
38697      * bounds - {<OpenLayers.Bounds>}
38698      */
38699     getURL: function(bounds) {
38700         return this.url;
38701     },
38702
38703     CLASS_NAME: "OpenLayers.Layer.Image"
38704 });
38705 /* ======================================================================
38706     OpenLayers/Layer/HTTPRequest.js
38707    ====================================================================== */
38708
38709 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
38710  * full list of contributors). Published under the 2-clause BSD license.
38711  * See license.txt in the OpenLayers distribution or repository for the
38712  * full text of the license. */
38713
38714
38715 /**
38716  * @requires OpenLayers/Layer.js
38717  */
38718
38719 /**
38720  * Class: OpenLayers.Layer.HTTPRequest
38721  * 
38722  * Inherits from: 
38723  *  - <OpenLayers.Layer>
38724  */
38725 OpenLayers.Layer.HTTPRequest = OpenLayers.Class(OpenLayers.Layer, {
38726
38727     /**
38728      * APIProperty: events
38729      * {<OpenLayers.Events>}
38730      *
38731      * Supported event types (in addition to those from <OpenLayers.Layer.events>):
38732      * refresh - Triggered when a redraw is forced, to re-fetch data from the
38733      *     server.
38734      */
38735
38736     /** 
38737      * Constant: URL_HASH_FACTOR
38738      * {Float} Used to hash URL param strings for multi-WMS server selection.
38739      *         Set to the Golden Ratio per Knuth's recommendation.
38740      */
38741     URL_HASH_FACTOR: (Math.sqrt(5) - 1) / 2,
38742
38743     /** 
38744      * Property: url
38745      * {Array(String) or String} This is either an array of url strings or 
38746      *                           a single url string. 
38747      */
38748     url: null,
38749
38750     /** 
38751      * Property: params
38752      * {Object} Hashtable of key/value parameters
38753      */
38754     params: null,
38755     
38756     /** 
38757      * APIProperty: reproject
38758      * *Deprecated*. See http://docs.openlayers.org/library/spherical_mercator.html
38759      * for information on the replacement for this functionality. 
38760      * {Boolean} Whether layer should reproject itself based on base layer 
38761      *           locations. This allows reprojection onto commercial layers. 
38762      *           Default is false: Most layers can't reproject, but layers 
38763      *           which can create non-square geographic pixels can, like WMS.
38764      *           
38765      */
38766     reproject: false,
38767
38768     /**
38769      * Constructor: OpenLayers.Layer.HTTPRequest
38770      * 
38771      * Parameters:
38772      * name - {String}
38773      * url - {Array(String) or String}
38774      * params - {Object}
38775      * options - {Object} Hashtable of extra options to tag onto the layer
38776      */
38777     initialize: function(name, url, params, options) {
38778         OpenLayers.Layer.prototype.initialize.apply(this, [name, options]);
38779         this.url = url;
38780         if (!this.params) {
38781             this.params = OpenLayers.Util.extend({}, params);
38782         }
38783     },
38784
38785     /**
38786      * APIMethod: destroy
38787      */
38788     destroy: function() {
38789         this.url = null;
38790         this.params = null;
38791         OpenLayers.Layer.prototype.destroy.apply(this, arguments); 
38792     },
38793     
38794     /**
38795      * APIMethod: clone
38796      * 
38797      * Parameters:
38798      * obj - {Object}
38799      * 
38800      * Returns:
38801      * {<OpenLayers.Layer.HTTPRequest>} An exact clone of this 
38802      *                                  <OpenLayers.Layer.HTTPRequest>
38803      */
38804     clone: function (obj) {
38805         
38806         if (obj == null) {
38807             obj = new OpenLayers.Layer.HTTPRequest(this.name,
38808                                                    this.url,
38809                                                    this.params,
38810                                                    this.getOptions());
38811         }
38812         
38813         //get all additions from superclasses
38814         obj = OpenLayers.Layer.prototype.clone.apply(this, [obj]);
38815
38816         // copy/set any non-init, non-simple values here
38817         
38818         return obj;
38819     },
38820
38821     /** 
38822      * APIMethod: setUrl
38823      * 
38824      * Parameters:
38825      * newUrl - {String}
38826      */
38827     setUrl: function(newUrl) {
38828         this.url = newUrl;
38829     },
38830
38831     /**
38832      * APIMethod: mergeNewParams
38833      * 
38834      * Parameters:
38835      * newParams - {Object}
38836      *
38837      * Returns:
38838      * redrawn: {Boolean} whether the layer was actually redrawn.
38839      */
38840     mergeNewParams:function(newParams) {
38841         this.params = OpenLayers.Util.extend(this.params, newParams);
38842         var ret = this.redraw();
38843         if(this.map != null) {
38844             this.map.events.triggerEvent("changelayer", {
38845                 layer: this,
38846                 property: "params"
38847             });
38848         }
38849         return ret;
38850     },
38851
38852     /**
38853      * APIMethod: redraw
38854      * Redraws the layer.  Returns true if the layer was redrawn, false if not.
38855      *
38856      * Parameters:
38857      * force - {Boolean} Force redraw by adding random parameter.
38858      *
38859      * Returns:
38860      * {Boolean} The layer was redrawn.
38861      */
38862     redraw: function(force) { 
38863         if (force) {
38864             this.events.triggerEvent('refresh');
38865             return this.mergeNewParams({"_olSalt": Math.random()});
38866         } else {
38867             return OpenLayers.Layer.prototype.redraw.apply(this, []);
38868         }
38869     },
38870     
38871     /**
38872      * Method: selectUrl
38873      * selectUrl() implements the standard floating-point multiplicative
38874      *     hash function described by Knuth, and hashes the contents of the 
38875      *     given param string into a float between 0 and 1. This float is then
38876      *     scaled to the size of the provided urls array, and used to select
38877      *     a URL.
38878      *
38879      * Parameters:
38880      * paramString - {String}
38881      * urls - {Array(String)}
38882      * 
38883      * Returns:
38884      * {String} An entry from the urls array, deterministically selected based
38885      *          on the paramString.
38886      */
38887     selectUrl: function(paramString, urls) {
38888         var product = 1;
38889         for (var i=0, len=paramString.length; i<len; i++) { 
38890             product *= paramString.charCodeAt(i) * this.URL_HASH_FACTOR; 
38891             product -= Math.floor(product); 
38892         }
38893         return urls[Math.floor(product * urls.length)];
38894     },
38895
38896     /** 
38897      * Method: getFullRequestString
38898      * Combine url with layer's params and these newParams. 
38899      *   
38900      *    does checking on the serverPath variable, allowing for cases when it 
38901      *     is supplied with trailing ? or &, as well as cases where not. 
38902      *
38903      *    return in formatted string like this:
38904      *        "server?key1=value1&key2=value2&key3=value3"
38905      * 
38906      * WARNING: The altUrl parameter is deprecated and will be removed in 3.0.
38907      *
38908      * Parameters:
38909      * newParams - {Object}
38910      * altUrl - {String} Use this as the url instead of the layer's url
38911      *   
38912      * Returns: 
38913      * {String}
38914      */
38915     getFullRequestString:function(newParams, altUrl) {
38916
38917         // if not altUrl passed in, use layer's url
38918         var url = altUrl || this.url;
38919         
38920         // create a new params hashtable with all the layer params and the 
38921         // new params together. then convert to string
38922         var allParams = OpenLayers.Util.extend({}, this.params);
38923         allParams = OpenLayers.Util.extend(allParams, newParams);
38924         var paramsString = OpenLayers.Util.getParameterString(allParams);
38925         
38926         // if url is not a string, it should be an array of strings, 
38927         // in which case we will deterministically select one of them in 
38928         // order to evenly distribute requests to different urls.
38929         //
38930         if (OpenLayers.Util.isArray(url)) {
38931             url = this.selectUrl(paramsString, url);
38932         }   
38933  
38934         // ignore parameters that are already in the url search string
38935         var urlParams = 
38936             OpenLayers.Util.upperCaseObject(OpenLayers.Util.getParameters(url));
38937         for(var key in allParams) {
38938             if(key.toUpperCase() in urlParams) {
38939                 delete allParams[key];
38940             }
38941         }
38942         paramsString = OpenLayers.Util.getParameterString(allParams);
38943         
38944         return OpenLayers.Util.urlAppend(url, paramsString);
38945     },
38946
38947     CLASS_NAME: "OpenLayers.Layer.HTTPRequest"
38948 });
38949 /* ======================================================================
38950     OpenLayers/Layer/Grid.js
38951    ====================================================================== */
38952
38953 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
38954  * full list of contributors). Published under the 2-clause BSD license.
38955  * See license.txt in the OpenLayers distribution or repository for the
38956  * full text of the license. */
38957
38958
38959 /**
38960  * @requires OpenLayers/Layer/HTTPRequest.js
38961  * @requires OpenLayers/Tile/Image.js
38962  */
38963
38964 /**
38965  * Class: OpenLayers.Layer.Grid
38966  * Base class for layers that use a lattice of tiles.  Create a new grid
38967  * layer with the <OpenLayers.Layer.Grid> constructor.
38968  *
38969  * Inherits from:
38970  *  - <OpenLayers.Layer.HTTPRequest>
38971  */
38972 OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
38973     
38974     /**
38975      * APIProperty: tileSize
38976      * {<OpenLayers.Size>}
38977      */
38978     tileSize: null,
38979
38980     /**
38981      * Property: tileOriginCorner
38982      * {String} If the <tileOrigin> property is not provided, the tile origin 
38983      *     will be derived from the layer's <maxExtent>.  The corner of the 
38984      *     <maxExtent> used is determined by this property.  Acceptable values
38985      *     are "tl" (top left), "tr" (top right), "bl" (bottom left), and "br"
38986      *     (bottom right).  Default is "bl".
38987      */
38988     tileOriginCorner: "bl",
38989     
38990     /**
38991      * APIProperty: tileOrigin
38992      * {<OpenLayers.LonLat>} Optional origin for aligning the grid of tiles.
38993      *     If provided, requests for tiles at all resolutions will be aligned
38994      *     with this location (no tiles shall overlap this location).  If
38995      *     not provided, the grid of tiles will be aligned with the layer's
38996      *     <maxExtent>.  Default is ``null``.
38997      */
38998     tileOrigin: null,
38999     
39000     /** APIProperty: tileOptions
39001      *  {Object} optional configuration options for <OpenLayers.Tile> instances
39002      *  created by this Layer, if supported by the tile class.
39003      */
39004     tileOptions: null,
39005
39006     /**
39007      * APIProperty: tileClass
39008      * {<OpenLayers.Tile>} The tile class to use for this layer.
39009      *     Defaults is OpenLayers.Tile.Image.
39010      */
39011     tileClass: OpenLayers.Tile.Image,
39012     
39013     /**
39014      * Property: grid
39015      * {Array(Array(<OpenLayers.Tile>))} This is an array of rows, each row is 
39016      *     an array of tiles.
39017      */
39018     grid: null,
39019
39020     /**
39021      * APIProperty: singleTile
39022      * {Boolean} Moves the layer into single-tile mode, meaning that one tile 
39023      *     will be loaded. The tile's size will be determined by the 'ratio'
39024      *     property. When the tile is dragged such that it does not cover the 
39025      *     entire viewport, it is reloaded.
39026      */
39027     singleTile: false,
39028
39029     /** APIProperty: ratio
39030      *  {Float} Used only when in single-tile mode, this specifies the 
39031      *          ratio of the size of the single tile to the size of the map.
39032      *          Default value is 1.5.
39033      */
39034     ratio: 1.5,
39035
39036     /**
39037      * APIProperty: buffer
39038      * {Integer} Used only when in gridded mode, this specifies the number of 
39039      *           extra rows and columns of tiles on each side which will
39040      *           surround the minimum grid tiles to cover the map.
39041      *           For very slow loading layers, a larger value may increase
39042      *           performance somewhat when dragging, but will increase bandwidth
39043      *           use significantly. 
39044      */
39045     buffer: 0,
39046
39047     /**
39048      * APIProperty: transitionEffect
39049      * {String} The transition effect to use when the map is zoomed.
39050      * Two possible values:
39051      *
39052      * "resize" - Existing tiles are resized on zoom to provide a visual
39053      *     effect of the zoom having taken place immediately.  As the
39054      *     new tiles become available, they are drawn on top of the
39055      *     resized tiles (this is the default setting).
39056      * "map-resize" - Existing tiles are resized on zoom and placed below the
39057      *     base layer.  New tiles for the base layer will cover existing tiles.
39058      *     This setting is recommended when having an overlay duplicated during
39059      *     the transition is undesirable (e.g. street labels or big transparent
39060      *     fills). 
39061      * null - No transition effect.
39062      *
39063      * Using "resize" on non-opaque layers can cause undesired visual
39064      * effects.  Set transitionEffect to null in this case.
39065      */
39066     transitionEffect: "resize",
39067
39068     /**
39069      * APIProperty: numLoadingTiles
39070      * {Integer} How many tiles are still loading?
39071      */
39072     numLoadingTiles: 0,
39073
39074     /**
39075      * Property: serverResolutions
39076      * {Array(Number}} This property is documented in subclasses as
39077      *     an API property.
39078      */
39079     serverResolutions: null,
39080
39081     /**
39082      * Property: loading
39083      * {Boolean} Indicates if tiles are being loaded.
39084      */
39085     loading: false,
39086     
39087     /**
39088      * Property: backBuffer
39089      * {DOMElement} The back buffer.
39090      */
39091     backBuffer: null,
39092
39093     /**
39094      * Property: gridResolution
39095      * {Number} The resolution of the current grid. Used for backbuffer and
39096      *     client zoom. This property is updated every time the grid is
39097      *     initialized.
39098      */
39099     gridResolution: null,
39100
39101     /**
39102      * Property: backBufferResolution
39103      * {Number} The resolution of the current back buffer. This property is
39104      *     updated each time a back buffer is created.
39105      */
39106     backBufferResolution: null,
39107
39108     /**
39109      * Property: backBufferLonLat
39110      * {Object} The top-left corner of the current back buffer. Includes lon
39111      *     and lat properties. This object is updated each time a back buffer
39112      *     is created.
39113      */
39114     backBufferLonLat: null,
39115
39116     /**
39117      * Property: backBufferTimerId
39118      * {Number} The id of the back buffer timer. This timer is used to
39119      *     delay the removal of the back buffer, thereby preventing
39120      *     flash effects caused by tile animation.
39121      */
39122     backBufferTimerId: null,
39123
39124     /**
39125      * APIProperty: removeBackBufferDelay
39126      * {Number} Delay for removing the backbuffer when all tiles have finished
39127      *     loading. Can be set to 0 when no css opacity transitions for the
39128      *     olTileImage class are used. Default is 0 for <singleTile> layers,
39129      *     2500 for tiled layers. See <className> for more information on
39130      *     tile animation.
39131      */
39132     removeBackBufferDelay: null,
39133
39134     /**
39135      * APIProperty: className
39136      * {String} Name of the class added to the layer div. If not set in the
39137      *     options passed to the constructor then className defaults to
39138      *     "olLayerGridSingleTile" for single tile layers (see <singleTile>),
39139      *     and "olLayerGrid" for non single tile layers.
39140      *
39141      * Note:
39142      *
39143      * The displaying of tiles is not animated by default for single tile
39144      *     layers - OpenLayers' default theme (style.css) includes this:
39145      * (code)
39146      * .olLayerGrid .olTileImage {
39147      *     -webkit-transition: opacity 0.2s linear;
39148      *     -moz-transition: opacity 0.2s linear;
39149      *     -o-transition: opacity 0.2s linear;
39150      *     transition: opacity 0.2s linear;
39151      *  }
39152      * (end)
39153      * To animate tile displaying for any grid layer the following
39154      *     CSS rule can be used:
39155      * (code)
39156      * .olTileImage {
39157      *     -webkit-transition: opacity 0.2s linear;
39158      *     -moz-transition: opacity 0.2s linear;
39159      *     -o-transition: opacity 0.2s linear;
39160      *     transition: opacity 0.2s linear;
39161      * }
39162      * (end)
39163      * In that case, to avoid flash effects, <removeBackBufferDelay>
39164      *     should not be zero.
39165      */
39166     className: null,
39167     
39168     /**
39169      * Register a listener for a particular event with the following syntax:
39170      * (code)
39171      * layer.events.register(type, obj, listener);
39172      * (end)
39173      *
39174      * Listeners will be called with a reference to an event object.  The
39175      *     properties of this event depends on exactly what happened.
39176      *
39177      * All event objects have at least the following properties:
39178      * object - {Object} A reference to layer.events.object.
39179      * element - {DOMElement} A reference to layer.events.element.
39180      *
39181      * Supported event types:
39182      * addtile - Triggered when a tile is added to this layer. Listeners receive
39183      *     an object as first argument, which has a tile property that
39184      *     references the tile that has been added.
39185      * tileloadstart - Triggered when a tile starts loading. Listeners receive
39186      *     an object as first argument, which has a tile property that
39187      *     references the tile that starts loading.
39188      * tileloaded - Triggered when each new tile is
39189      *     loaded, as a means of progress update to listeners.
39190      *     listeners can access 'numLoadingTiles' if they wish to keep
39191      *     track of the loading progress. Listeners are called with an object
39192      *     with a 'tile' property as first argument, making the loaded tile
39193      *     available to the listener, and an 'aborted' property, which will be
39194      *     true when loading was aborted and no tile data is available.
39195      * tileerror - Triggered before the tileloaded event (i.e. when the tile is
39196      *     still hidden) if a tile failed to load. Listeners receive an object
39197      *     as first argument, which has a tile property that references the
39198      *     tile that could not be loaded.
39199      * retile - Triggered when the layer recreates its tile grid.
39200      */
39201
39202     /**
39203      * Property: gridLayout
39204      * {Object} Object containing properties tilelon, tilelat, startcol,
39205      * startrow
39206      */
39207     gridLayout: null,
39208     
39209     /**
39210      * Property: rowSign
39211      * {Number} 1 for grids starting at the top, -1 for grids starting at the
39212      * bottom. This is used for several grid index and offset calculations.
39213      */
39214     rowSign: null,
39215
39216     /**
39217      * Property: transitionendEvents
39218      * {Array} Event names for transitionend
39219      */
39220     transitionendEvents: [
39221         'transitionend', 'webkitTransitionEnd', 'otransitionend',
39222         'oTransitionEnd'
39223     ],
39224
39225     /**
39226      * Constructor: OpenLayers.Layer.Grid
39227      * Create a new grid layer
39228      *
39229      * Parameters:
39230      * name - {String}
39231      * url - {String}
39232      * params - {Object}
39233      * options - {Object} Hashtable of extra options to tag onto the layer
39234      */
39235     initialize: function(name, url, params, options) {
39236         OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this, 
39237                                                                 arguments);
39238         this.grid = [];
39239         this._removeBackBuffer = OpenLayers.Function.bind(this.removeBackBuffer, this);
39240
39241         this.initProperties();
39242
39243         this.rowSign = this.tileOriginCorner.substr(0, 1) === "t" ? 1 : -1;
39244     },
39245
39246     /**
39247      * Method: initProperties
39248      * Set any properties that depend on the value of singleTile.
39249      * Currently sets removeBackBufferDelay and className
39250      */
39251     initProperties: function() {
39252         if (this.options.removeBackBufferDelay === undefined) {
39253             this.removeBackBufferDelay = this.singleTile ? 0 : 2500;
39254         }
39255
39256         if (this.options.className === undefined) {
39257             this.className = this.singleTile ? 'olLayerGridSingleTile' :
39258                                                'olLayerGrid';
39259         }
39260     },
39261
39262     /**
39263      * Method: setMap
39264      *
39265      * Parameters:
39266      * map - {<OpenLayers.Map>} The map.
39267      */
39268     setMap: function(map) {
39269         OpenLayers.Layer.HTTPRequest.prototype.setMap.call(this, map);
39270         OpenLayers.Element.addClass(this.div, this.className);
39271     },
39272
39273     /**
39274      * Method: removeMap
39275      * Called when the layer is removed from the map.
39276      *
39277      * Parameters:
39278      * map - {<OpenLayers.Map>} The map.
39279      */
39280     removeMap: function(map) {
39281         this.removeBackBuffer();
39282     },
39283
39284     /**
39285      * APIMethod: destroy
39286      * Deconstruct the layer and clear the grid.
39287      */
39288     destroy: function() {
39289         this.removeBackBuffer();
39290         this.clearGrid();
39291
39292         this.grid = null;
39293         this.tileSize = null;
39294         OpenLayers.Layer.HTTPRequest.prototype.destroy.apply(this, arguments); 
39295     },
39296
39297     /**
39298      * APIMethod: mergeNewParams
39299      * Refetches tiles with new params merged, keeping a backbuffer. Each
39300      * loading new tile will have a css class of '.olTileReplacing'. If a
39301      * stylesheet applies a 'display: none' style to that class, any fade-in
39302      * transition will not apply, and backbuffers for each tile will be removed
39303      * as soon as the tile is loaded.
39304      * 
39305      * Parameters:
39306      * newParams - {Object}
39307      *
39308      * Returns:
39309      * redrawn: {Boolean} whether the layer was actually redrawn.
39310      */
39311
39312     /**
39313      * Method: clearGrid
39314      * Go through and remove all tiles from the grid, calling
39315      *    destroy() on each of them to kill circular references
39316      */
39317     clearGrid:function() {
39318         if (this.grid) {
39319             for(var iRow=0, len=this.grid.length; iRow<len; iRow++) {
39320                 var row = this.grid[iRow];
39321                 for(var iCol=0, clen=row.length; iCol<clen; iCol++) {
39322                     var tile = row[iCol];
39323                     this.destroyTile(tile);
39324                 }
39325             }
39326             this.grid = [];
39327             this.gridResolution = null;
39328             this.gridLayout = null;
39329         }
39330     },
39331
39332    /**
39333     * APIMethod: addOptions
39334     * 
39335     * Parameters:
39336     * newOptions - {Object}
39337     * reinitialize - {Boolean} If set to true, and if resolution options of the
39338     *     current baseLayer were changed, the map will be recentered to make
39339     *     sure that it is displayed with a valid resolution, and a
39340     *     changebaselayer event will be triggered.
39341     */
39342     addOptions: function (newOptions, reinitialize) {
39343         var singleTileChanged = newOptions.singleTile !== undefined && 
39344             newOptions.singleTile !== this.singleTile;
39345         OpenLayers.Layer.HTTPRequest.prototype.addOptions.apply(this, arguments);
39346         if (this.map && singleTileChanged) {
39347             this.initProperties();
39348             this.clearGrid();
39349             this.tileSize = this.options.tileSize;
39350             this.setTileSize();
39351             if (this.visibility) {
39352                 this.moveTo(null, true);
39353             }
39354         }
39355     },
39356     
39357     /**
39358      * APIMethod: clone
39359      * Create a clone of this layer
39360      *
39361      * Parameters:
39362      * obj - {Object} Is this ever used?
39363      * 
39364      * Returns:
39365      * {<OpenLayers.Layer.Grid>} An exact clone of this OpenLayers.Layer.Grid
39366      */
39367     clone: function (obj) {
39368         
39369         if (obj == null) {
39370             obj = new OpenLayers.Layer.Grid(this.name,
39371                                             this.url,
39372                                             this.params,
39373                                             this.getOptions());
39374         }
39375
39376         //get all additions from superclasses
39377         obj = OpenLayers.Layer.HTTPRequest.prototype.clone.apply(this, [obj]);
39378
39379         // copy/set any non-init, non-simple values here
39380         if (this.tileSize != null) {
39381             obj.tileSize = this.tileSize.clone();
39382         }
39383         
39384         // we do not want to copy reference to grid, so we make a new array
39385         obj.grid = [];
39386         obj.gridResolution = null;
39387         // same for backbuffer
39388         obj.backBuffer = null;
39389         obj.backBufferTimerId = null;
39390         obj.loading = false;
39391         obj.numLoadingTiles = 0;
39392
39393         return obj;
39394     },    
39395
39396     /**
39397      * Method: moveTo
39398      * This function is called whenever the map is moved. All the moving
39399      * of actual 'tiles' is done by the map, but moveTo's role is to accept
39400      * a bounds and make sure the data that that bounds requires is pre-loaded.
39401      *
39402      * Parameters:
39403      * bounds - {<OpenLayers.Bounds>}
39404      * zoomChanged - {Boolean}
39405      * dragging - {Boolean}
39406      */
39407     moveTo:function(bounds, zoomChanged, dragging) {
39408
39409         OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this, arguments);
39410
39411         bounds = bounds || this.map.getExtent();
39412
39413         if (bounds != null) {
39414              
39415             // if grid is empty or zoom has changed, we *must* re-tile
39416             var forceReTile = !this.grid.length || zoomChanged;
39417             
39418             // total bounds of the tiles
39419             var tilesBounds = this.getTilesBounds();            
39420
39421             // the new map resolution
39422             var resolution = this.map.getResolution();
39423
39424             // the server-supported resolution for the new map resolution
39425             var serverResolution = this.getServerResolution(resolution);
39426
39427             if (this.singleTile) {
39428                 
39429                 // We want to redraw whenever even the slightest part of the 
39430                 //  current bounds is not contained by our tile.
39431                 //  (thus, we do not specify partial -- its default is false)
39432
39433                 if ( forceReTile ||
39434                      (!dragging && !tilesBounds.containsBounds(bounds))) {
39435
39436                     // In single tile mode with no transition effect, we insert
39437                     // a non-scaled backbuffer when the layer is moved. But if
39438                     // a zoom occurs right after a move, i.e. before the new
39439                     // image is received, we need to remove the backbuffer, or
39440                     // an ill-positioned image will be visible during the zoom
39441                     // transition.
39442
39443                     if(zoomChanged && this.transitionEffect !== 'resize') {
39444                         this.removeBackBuffer();
39445                     }
39446
39447                     if(!zoomChanged || this.transitionEffect === 'resize') {
39448                         this.applyBackBuffer(resolution);
39449                     }
39450
39451                     this.initSingleTile(bounds);
39452                 }
39453             } else {
39454
39455                 // if the bounds have changed such that they are not even 
39456                 // *partially* contained by our tiles (e.g. when user has 
39457                 // programmatically panned to the other side of the earth on
39458                 // zoom level 18), then moveGriddedTiles could potentially have
39459                 // to run through thousands of cycles, so we want to reTile
39460                 // instead (thus, partial true).  
39461                 forceReTile = forceReTile ||
39462                     !tilesBounds.intersectsBounds(bounds, {
39463                         worldBounds: this.map.baseLayer.wrapDateLine &&
39464                             this.map.getMaxExtent()
39465                     });
39466
39467                 if(forceReTile) {
39468                     if(zoomChanged && (this.transitionEffect === 'resize' ||
39469                                           this.gridResolution === resolution)) {
39470                         this.applyBackBuffer(resolution);
39471                     }
39472                     this.initGriddedTiles(bounds);
39473                 } else {
39474                     this.moveGriddedTiles();
39475                 }
39476             }
39477         }
39478     },
39479
39480     /**
39481      * Method: getTileData
39482      * Given a map location, retrieve a tile and the pixel offset within that
39483      *     tile corresponding to the location.  If there is not an existing 
39484      *     tile in the grid that covers the given location, null will be 
39485      *     returned.
39486      *
39487      * Parameters:
39488      * loc - {<OpenLayers.LonLat>} map location
39489      *
39490      * Returns:
39491      * {Object} Object with the following properties: tile ({<OpenLayers.Tile>}),
39492      *     i ({Number} x-pixel offset from top left), and j ({Integer} y-pixel
39493      *     offset from top left).
39494      */
39495     getTileData: function(loc) {
39496         var data = null,
39497             x = loc.lon,
39498             y = loc.lat,
39499             numRows = this.grid.length;
39500
39501         if (this.map && numRows) {
39502             var res = this.map.getResolution(),
39503                 tileWidth = this.tileSize.w,
39504                 tileHeight = this.tileSize.h,
39505                 bounds = this.grid[0][0].bounds,
39506                 left = bounds.left,
39507                 top = bounds.top;
39508
39509             if (x < left) {
39510                 // deal with multiple worlds
39511                 if (this.map.baseLayer.wrapDateLine) {
39512                     var worldWidth = this.map.getMaxExtent().getWidth();
39513                     var worldsAway = Math.ceil((left - x) / worldWidth);
39514                     x += worldWidth * worldsAway;
39515                 }
39516             }
39517             // tile distance to location (fractional number of tiles);
39518             var dtx = (x - left) / (res * tileWidth);
39519             var dty = (top - y) / (res * tileHeight);
39520             // index of tile in grid
39521             var col = Math.floor(dtx);
39522             var row = Math.floor(dty);
39523             if (row >= 0 && row < numRows) {
39524                 var tile = this.grid[row][col];
39525                 if (tile) {
39526                     data = {
39527                         tile: tile,
39528                         // pixel index within tile
39529                         i: Math.floor((dtx - col) * tileWidth),
39530                         j: Math.floor((dty - row) * tileHeight)
39531                     };                    
39532                 }
39533             }
39534         }
39535         return data;
39536     },
39537     
39538     /**
39539      * Method: destroyTile
39540      *
39541      * Parameters:
39542      * tile - {<OpenLayers.Tile>}
39543      */
39544     destroyTile: function(tile) {
39545         this.removeTileMonitoringHooks(tile);
39546         tile.destroy();
39547     },
39548
39549     /**
39550      * Method: getServerResolution
39551      * Return the closest server-supported resolution.
39552      *
39553      * Parameters:
39554      * resolution - {Number} The base resolution. If undefined the
39555      *     map resolution is used.
39556      *
39557      * Returns:
39558      * {Number} The closest server resolution value.
39559      */
39560     getServerResolution: function(resolution) {
39561         var distance = Number.POSITIVE_INFINITY;
39562         resolution = resolution || this.map.getResolution();
39563         if(this.serverResolutions &&
39564            OpenLayers.Util.indexOf(this.serverResolutions, resolution) === -1) {
39565             var i, newDistance, newResolution, serverResolution;
39566             for(i=this.serverResolutions.length-1; i>= 0; i--) {
39567                 newResolution = this.serverResolutions[i];
39568                 newDistance = Math.abs(newResolution - resolution);
39569                 if (newDistance > distance) {
39570                     break;
39571                 }
39572                 distance = newDistance;
39573                 serverResolution = newResolution;
39574             }
39575             resolution = serverResolution;
39576         }
39577         return resolution;
39578     },
39579
39580     /**
39581      * Method: getServerZoom
39582      * Return the zoom value corresponding to the best matching server
39583      * resolution, taking into account <serverResolutions> and <zoomOffset>.
39584      *
39585      * Returns:
39586      * {Number} The closest server supported zoom. This is not the map zoom
39587      *     level, but an index of the server's resolutions array.
39588      */
39589     getServerZoom: function() {
39590         var resolution = this.getServerResolution();
39591         return this.serverResolutions ?
39592             OpenLayers.Util.indexOf(this.serverResolutions, resolution) :
39593             this.map.getZoomForResolution(resolution) + (this.zoomOffset || 0);
39594     },
39595
39596     /**
39597      * Method: applyBackBuffer
39598      * Create, insert, scale and position a back buffer for the layer.
39599      *
39600      * Parameters:
39601      * resolution - {Number} The resolution to transition to.
39602      */
39603     applyBackBuffer: function(resolution) {
39604         if(this.backBufferTimerId !== null) {
39605             this.removeBackBuffer();
39606         }
39607         var backBuffer = this.backBuffer;
39608         if(!backBuffer) {
39609             backBuffer = this.createBackBuffer();
39610             if(!backBuffer) {
39611                 return;
39612             }
39613             if (resolution === this.gridResolution) {
39614                 this.div.insertBefore(backBuffer, this.div.firstChild);
39615             } else {
39616                 this.map.baseLayer.div.parentNode.insertBefore(backBuffer, this.map.baseLayer.div);
39617             }
39618             this.backBuffer = backBuffer;
39619
39620             // set some information in the instance for subsequent
39621             // calls to applyBackBuffer where the same back buffer
39622             // is reused
39623             var topLeftTileBounds = this.grid[0][0].bounds;
39624             this.backBufferLonLat = {
39625                 lon: topLeftTileBounds.left,
39626                 lat: topLeftTileBounds.top
39627             };
39628             this.backBufferResolution = this.gridResolution;
39629         }
39630         
39631         var ratio = this.backBufferResolution / resolution;
39632
39633         // scale the tiles inside the back buffer
39634         var tiles = backBuffer.childNodes, tile;
39635         for (var i=tiles.length-1; i>=0; --i) {
39636             tile = tiles[i];
39637             tile.style.top = ((ratio * tile._i * backBuffer._th) | 0) + 'px';
39638             tile.style.left = ((ratio * tile._j * backBuffer._tw) | 0) + 'px';
39639             tile.style.width = Math.round(ratio * tile._w) + 'px';
39640             tile.style.height = Math.round(ratio * tile._h) + 'px';
39641         }
39642
39643         // and position it (based on the grid's top-left corner)
39644         var position = this.getViewPortPxFromLonLat(
39645                 this.backBufferLonLat, resolution);
39646         var leftOffset = this.map.layerContainerOriginPx.x;
39647         var topOffset = this.map.layerContainerOriginPx.y;
39648         backBuffer.style.left = Math.round(position.x - leftOffset) + 'px';
39649         backBuffer.style.top = Math.round(position.y - topOffset) + 'px';
39650     },
39651
39652     /**
39653      * Method: createBackBuffer
39654      * Create a back buffer.
39655      *
39656      * Returns:
39657      * {DOMElement} The DOM element for the back buffer, undefined if the
39658      * grid isn't initialized yet.
39659      */
39660     createBackBuffer: function() {
39661         var backBuffer;
39662         if(this.grid.length > 0) {
39663             backBuffer = document.createElement('div');
39664             backBuffer.id = this.div.id + '_bb';
39665             backBuffer.className = 'olBackBuffer';
39666             backBuffer.style.position = 'absolute';
39667             var map = this.map;
39668             backBuffer.style.zIndex = this.transitionEffect === 'resize' ?
39669                     this.getZIndex() - 1 :
39670                     // 'map-resize':
39671                     map.Z_INDEX_BASE.BaseLayer -
39672                             (map.getNumLayers() - map.getLayerIndex(this));
39673             for(var i=0, lenI=this.grid.length; i<lenI; i++) {
39674                 for(var j=0, lenJ=this.grid[i].length; j<lenJ; j++) {
39675                     var tile = this.grid[i][j],
39676                         markup = this.grid[i][j].createBackBuffer();
39677                     if (markup) {
39678                         markup._i = i;
39679                         markup._j = j;
39680                         markup._w = this.singleTile ?
39681                                 this.getImageSize(tile.bounds).w : tile.size.w;
39682                         markup._h = tile.size.h;
39683                         markup.id = tile.id + '_bb';
39684                         backBuffer.appendChild(markup);
39685                     }
39686                 }
39687             }
39688             backBuffer._tw = this.tileSize.w;
39689             backBuffer._th = this.tileSize.h;
39690         }
39691         return backBuffer;
39692     },
39693
39694     /**
39695      * Method: removeBackBuffer
39696      * Remove back buffer from DOM.
39697      */
39698     removeBackBuffer: function() {
39699         if (this._transitionElement) {
39700             for (var i=this.transitionendEvents.length-1; i>=0; --i) {
39701                 OpenLayers.Event.stopObserving(this._transitionElement,
39702                     this.transitionendEvents[i], this._removeBackBuffer);
39703             }
39704             delete this._transitionElement;
39705         }
39706         if(this.backBuffer) {
39707             if (this.backBuffer.parentNode) {
39708                 this.backBuffer.parentNode.removeChild(this.backBuffer);
39709             }
39710             this.backBuffer = null;
39711             this.backBufferResolution = null;
39712             if(this.backBufferTimerId !== null) {
39713                 window.clearTimeout(this.backBufferTimerId);
39714                 this.backBufferTimerId = null;
39715             }
39716         }
39717     },
39718
39719     /**
39720      * Method: moveByPx
39721      * Move the layer based on pixel vector.
39722      *
39723      * Parameters:
39724      * dx - {Number}
39725      * dy - {Number}
39726      */
39727     moveByPx: function(dx, dy) {
39728         if (!this.singleTile) {
39729             this.moveGriddedTiles();
39730         }
39731     },
39732
39733     /**
39734      * APIMethod: setTileSize
39735      * Check if we are in singleTile mode and if so, set the size as a ratio
39736      *     of the map size (as specified by the layer's 'ratio' property).
39737      * 
39738      * Parameters:
39739      * size - {<OpenLayers.Size>}
39740      */
39741     setTileSize: function(size) { 
39742         if (this.singleTile) {
39743             size = this.map.getSize();
39744             size.h = parseInt(size.h * this.ratio, 10);
39745             size.w = parseInt(size.w * this.ratio, 10);
39746         } 
39747         OpenLayers.Layer.HTTPRequest.prototype.setTileSize.apply(this, [size]);
39748     },
39749
39750     /**
39751      * APIMethod: getTilesBounds
39752      * Return the bounds of the tile grid.
39753      *
39754      * Returns:
39755      * {<OpenLayers.Bounds>} A Bounds object representing the bounds of all the
39756      *     currently loaded tiles (including those partially or not at all seen 
39757      *     onscreen).
39758      */
39759     getTilesBounds: function() {    
39760         var bounds = null; 
39761         
39762         var length = this.grid.length;
39763         if (length) {
39764             var bottomLeftTileBounds = this.grid[length - 1][0].bounds,
39765                 width = this.grid[0].length * bottomLeftTileBounds.getWidth(),
39766                 height = this.grid.length * bottomLeftTileBounds.getHeight();
39767             
39768             bounds = new OpenLayers.Bounds(bottomLeftTileBounds.left, 
39769                                            bottomLeftTileBounds.bottom,
39770                                            bottomLeftTileBounds.left + width, 
39771                                            bottomLeftTileBounds.bottom + height);
39772         }   
39773         return bounds;
39774     },
39775
39776     /**
39777      * Method: initSingleTile
39778      * 
39779      * Parameters: 
39780      * bounds - {<OpenLayers.Bounds>}
39781      */
39782     initSingleTile: function(bounds) {
39783         this.events.triggerEvent("retile");
39784
39785         //determine new tile bounds
39786         var center = bounds.getCenterLonLat();
39787         var tileWidth = bounds.getWidth() * this.ratio;
39788         var tileHeight = bounds.getHeight() * this.ratio;
39789                                        
39790         var tileBounds = 
39791             new OpenLayers.Bounds(center.lon - (tileWidth/2),
39792                                   center.lat - (tileHeight/2),
39793                                   center.lon + (tileWidth/2),
39794                                   center.lat + (tileHeight/2));
39795
39796         // store the resolution of the grid
39797         this.gridResolution = this.getServerResolution();
39798   
39799         // same logic as OpenLayers.Tile#shouldDraw
39800         var maxExtent = this.maxExtent;
39801         if (maxExtent && (!this.displayOutsideMaxExtent ||
39802                 (this.map.baseLayer.wrapDateLine &&
39803                 this.maxExtent.equals(this.map.getMaxExtent())))) {
39804             tileBounds.left = Math.max(tileBounds.left, maxExtent.left);
39805             tileBounds.right = Math.min(tileBounds.right, maxExtent.right);
39806         }
39807
39808         var px = this.map.getLayerPxFromLonLat({
39809             lon: tileBounds.left,
39810             lat: tileBounds.top
39811         });
39812
39813         if (!this.grid.length) {
39814             this.grid[0] = [];
39815         }
39816
39817         var tile = this.grid[0][0];
39818         if (!tile) {
39819             tile = this.addTile(tileBounds, px);
39820
39821             this.addTileMonitoringHooks(tile);
39822             tile.draw();
39823             this.grid[0][0] = tile;
39824         } else {
39825             tile.moveTo(tileBounds, px);
39826         }           
39827         
39828         //remove all but our single tile
39829         this.removeExcessTiles(1,1);
39830     },
39831
39832     /** 
39833      * Method: calculateGridLayout
39834      * Generate parameters for the grid layout.
39835      *
39836      * Parameters:
39837      * bounds - {<OpenLayers.Bound>|Object} OpenLayers.Bounds or an
39838      *     object with a 'left' and 'top' properties.
39839      * origin - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
39840      *     object with a 'lon' and 'lat' properties.
39841      * resolution - {Number}
39842      *
39843      * Returns:
39844      * {Object} Object containing properties tilelon, tilelat, startcol,
39845      * startrow
39846      */
39847     calculateGridLayout: function(bounds, origin, resolution) {
39848         var tilelon = resolution * this.tileSize.w;
39849         var tilelat = resolution * this.tileSize.h;
39850         
39851         var offsetlon = bounds.left - origin.lon;
39852         var tilecol = Math.floor(offsetlon/tilelon) - this.buffer;
39853         
39854         var rowSign = this.rowSign;
39855
39856         var offsetlat = rowSign * (origin.lat - bounds.top + tilelat);  
39857         var tilerow = Math[~rowSign ? 'floor' : 'ceil'](offsetlat/tilelat) - this.buffer * rowSign;
39858         
39859         return { 
39860           tilelon: tilelon, tilelat: tilelat,
39861           startcol: tilecol, startrow: tilerow
39862         };
39863
39864     },
39865
39866     getImageSize: function(bounds) {
39867         var tileSize = OpenLayers.Layer.HTTPRequest.prototype.getImageSize.apply(this, arguments);
39868         if (this.singleTile) {
39869             tileSize = new OpenLayers.Size(
39870                 Math.round(bounds.getWidth() / this.gridResolution),
39871                 tileSize.h
39872             );
39873         }
39874         return tileSize;
39875     },
39876
39877     /**
39878      * Method: getTileOrigin
39879      * Determine the origin for aligning the grid of tiles.  If a <tileOrigin>
39880      *     property is supplied, that will be returned.  Otherwise, the origin
39881      *     will be derived from the layer's <maxExtent> property.  In this case,
39882      *     the tile origin will be the corner of the <maxExtent> given by the 
39883      *     <tileOriginCorner> property.
39884      *
39885      * Returns:
39886      * {<OpenLayers.LonLat>} The tile origin.
39887      */
39888     getTileOrigin: function() {
39889         var origin = this.tileOrigin;
39890         if (!origin) {
39891             var extent = this.getMaxExtent();
39892             var edges = ({
39893                 "tl": ["left", "top"],
39894                 "tr": ["right", "top"],
39895                 "bl": ["left", "bottom"],
39896                 "br": ["right", "bottom"]
39897             })[this.tileOriginCorner];
39898             origin = new OpenLayers.LonLat(extent[edges[0]], extent[edges[1]]);
39899         }
39900         return origin;
39901     },
39902
39903     /**
39904      * Method: getTileBoundsForGridIndex
39905      *
39906      * Parameters:
39907      * row - {Number} The row of the grid
39908      * col - {Number} The column of the grid
39909      *
39910      * Returns:
39911      * {<OpenLayers.Bounds>} The bounds for the tile at (row, col)
39912      */
39913     getTileBoundsForGridIndex: function(row, col) {
39914         var origin = this.getTileOrigin();
39915         var tileLayout = this.gridLayout;
39916         var tilelon = tileLayout.tilelon;
39917         var tilelat = tileLayout.tilelat;
39918         var startcol = tileLayout.startcol;
39919         var startrow = tileLayout.startrow;
39920         var rowSign = this.rowSign;
39921         return new OpenLayers.Bounds(
39922             origin.lon + (startcol + col) * tilelon,
39923             origin.lat - (startrow + row * rowSign) * tilelat * rowSign,
39924             origin.lon + (startcol + col + 1) * tilelon,
39925             origin.lat - (startrow + (row - 1) * rowSign) * tilelat * rowSign
39926         );
39927     },
39928
39929     /**
39930      * Method: initGriddedTiles
39931      * 
39932      * Parameters:
39933      * bounds - {<OpenLayers.Bounds>}
39934      */
39935     initGriddedTiles:function(bounds) {
39936         this.events.triggerEvent("retile");
39937
39938         // work out mininum number of rows and columns; this is the number of
39939         // tiles required to cover the viewport plus at least one for panning
39940
39941         var viewSize = this.map.getSize();
39942         
39943         var origin = this.getTileOrigin();
39944         var resolution = this.map.getResolution(),
39945             serverResolution = this.getServerResolution(),
39946             ratio = resolution / serverResolution,
39947             tileSize = {
39948                 w: this.tileSize.w / ratio,
39949                 h: this.tileSize.h / ratio
39950             };
39951
39952         var minRows = Math.ceil(viewSize.h/tileSize.h) + 
39953                       2 * this.buffer + 1;
39954         var minCols = Math.ceil(viewSize.w/tileSize.w) +
39955                       2 * this.buffer + 1;
39956
39957         var tileLayout = this.calculateGridLayout(bounds, origin, serverResolution);
39958         this.gridLayout = tileLayout;
39959         
39960         var tilelon = tileLayout.tilelon;
39961         var tilelat = tileLayout.tilelat;
39962         
39963         var layerContainerDivLeft = this.map.layerContainerOriginPx.x;
39964         var layerContainerDivTop = this.map.layerContainerOriginPx.y;
39965
39966         var tileBounds = this.getTileBoundsForGridIndex(0, 0);
39967         var startPx = this.map.getViewPortPxFromLonLat(
39968             new OpenLayers.LonLat(tileBounds.left, tileBounds.top)
39969         );
39970         startPx.x = Math.round(startPx.x) - layerContainerDivLeft;
39971         startPx.y = Math.round(startPx.y) - layerContainerDivTop;
39972
39973         var tileData = [], center = this.map.getCenter();
39974
39975         var rowidx = 0;
39976         do {
39977             var row = this.grid[rowidx];
39978             if (!row) {
39979                 row = [];
39980                 this.grid.push(row);
39981             }
39982             
39983             var colidx = 0;
39984             do {
39985                 tileBounds = this.getTileBoundsForGridIndex(rowidx, colidx);
39986                 var px = startPx.clone();
39987                 px.x = px.x + colidx * Math.round(tileSize.w);
39988                 px.y = px.y + rowidx * Math.round(tileSize.h);
39989                 var tile = row[colidx];
39990                 if (!tile) {
39991                     tile = this.addTile(tileBounds, px);
39992                     this.addTileMonitoringHooks(tile);
39993                     row.push(tile);
39994                 } else {
39995                     tile.moveTo(tileBounds, px, false);
39996                 }
39997                 var tileCenter = tileBounds.getCenterLonLat();
39998                 tileData.push({
39999                     tile: tile,
40000                     distance: Math.pow(tileCenter.lon - center.lon, 2) +
40001                         Math.pow(tileCenter.lat - center.lat, 2)
40002                 });
40003      
40004                 colidx += 1;
40005             } while ((tileBounds.right <= bounds.right + tilelon * this.buffer)
40006                      || colidx < minCols);
40007              
40008             rowidx += 1;
40009         } while((tileBounds.bottom >= bounds.bottom - tilelat * this.buffer)
40010                 || rowidx < minRows);
40011         
40012         //shave off excess rows and columns
40013         this.removeExcessTiles(rowidx, colidx);
40014
40015         var resolution = this.getServerResolution();
40016         // store the resolution of the grid
40017         this.gridResolution = resolution;
40018
40019         //now actually draw the tiles
40020         tileData.sort(function(a, b) {
40021             return a.distance - b.distance; 
40022         });
40023         for (var i=0, ii=tileData.length; i<ii; ++i) {
40024             tileData[i].tile.draw();
40025         }
40026     },
40027
40028     /**
40029      * Method: getMaxExtent
40030      * Get this layer's maximum extent. (Implemented as a getter for
40031      *     potential specific implementations in sub-classes.)
40032      *
40033      * Returns:
40034      * {<OpenLayers.Bounds>}
40035      */
40036     getMaxExtent: function() {
40037         return this.maxExtent;
40038     },
40039
40040     /**
40041      * APIMethod: addTile
40042      * Create a tile, initialize it, and add it to the layer div. 
40043      *
40044      * Parameters
40045      * bounds - {<OpenLayers.Bounds>}
40046      * position - {<OpenLayers.Pixel>}
40047      *
40048      * Returns:
40049      * {<OpenLayers.Tile>} The added OpenLayers.Tile
40050      */
40051     addTile: function(bounds, position) {
40052         var tile = new this.tileClass(
40053             this, position, bounds, null, this.tileSize, this.tileOptions
40054         );
40055         this.events.triggerEvent("addtile", {tile: tile});
40056         return tile;
40057     },
40058     
40059     /** 
40060      * Method: addTileMonitoringHooks
40061      * This function takes a tile as input and adds the appropriate hooks to 
40062      *     the tile so that the layer can keep track of the loading tiles.
40063      * 
40064      * Parameters: 
40065      * tile - {<OpenLayers.Tile>}
40066      */
40067     addTileMonitoringHooks: function(tile) {
40068         
40069         var replacingCls = 'olTileReplacing';
40070
40071         tile.onLoadStart = function() {
40072             //if that was first tile then trigger a 'loadstart' on the layer
40073             if (this.loading === false) {
40074                 this.loading = true;
40075                 this.events.triggerEvent("loadstart");
40076             }
40077             this.events.triggerEvent("tileloadstart", {tile: tile});
40078             this.numLoadingTiles++;
40079             if (!this.singleTile && this.backBuffer && this.gridResolution === this.backBufferResolution) {
40080                 OpenLayers.Element.addClass(tile.getTile(), replacingCls);
40081             }
40082         };
40083       
40084         tile.onLoadEnd = function(evt) {
40085             this.numLoadingTiles--;
40086             var aborted = evt.type === 'unload';
40087             this.events.triggerEvent("tileloaded", {
40088                 tile: tile,
40089                 aborted: aborted
40090             });
40091             if (!this.singleTile && !aborted && this.backBuffer && this.gridResolution === this.backBufferResolution) {
40092                 var tileDiv = tile.getTile();
40093                 if (OpenLayers.Element.getStyle(tileDiv, 'display') === 'none') {
40094                     var bufferTile = document.getElementById(tile.id + '_bb');
40095                     if (bufferTile) {
40096                         bufferTile.parentNode.removeChild(bufferTile);
40097                     }
40098                 }
40099                 OpenLayers.Element.removeClass(tileDiv, replacingCls);
40100             }
40101             //if that was the last tile, then trigger a 'loadend' on the layer
40102             if (this.numLoadingTiles === 0) {
40103                 if (this.backBuffer) {
40104                     if (this.backBuffer.childNodes.length === 0) {
40105                         // no tiles transitioning, remove immediately
40106                         this.removeBackBuffer();
40107                     } else {
40108                         // wait until transition has ended or delay has passed
40109                         this._transitionElement = aborted ?
40110                             this.div.lastChild : tile.imgDiv;
40111                         var transitionendEvents = this.transitionendEvents;
40112                         for (var i=transitionendEvents.length-1; i>=0; --i) {
40113                             OpenLayers.Event.observe(this._transitionElement,
40114                                 transitionendEvents[i],
40115                                 this._removeBackBuffer);
40116                         }
40117                         // the removal of the back buffer is delayed to prevent
40118                         // flash effects due to the animation of tile displaying
40119                         this.backBufferTimerId = window.setTimeout(
40120                             this._removeBackBuffer, this.removeBackBufferDelay
40121                         );
40122                     }
40123                 }
40124                 this.loading = false;
40125                 this.events.triggerEvent("loadend");
40126             }
40127         };
40128         
40129         tile.onLoadError = function() {
40130             this.events.triggerEvent("tileerror", {tile: tile});
40131         };
40132         
40133         tile.events.on({
40134             "loadstart": tile.onLoadStart,
40135             "loadend": tile.onLoadEnd,
40136             "unload": tile.onLoadEnd,
40137             "loaderror": tile.onLoadError,
40138             scope: this
40139         });
40140     },
40141
40142     /** 
40143      * Method: removeTileMonitoringHooks
40144      * This function takes a tile as input and removes the tile hooks 
40145      *     that were added in addTileMonitoringHooks()
40146      * 
40147      * Parameters: 
40148      * tile - {<OpenLayers.Tile>}
40149      */
40150     removeTileMonitoringHooks: function(tile) {
40151         tile.unload();
40152         tile.events.un({
40153             "loadstart": tile.onLoadStart,
40154             "loadend": tile.onLoadEnd,
40155             "unload": tile.onLoadEnd,
40156             "loaderror": tile.onLoadError,
40157             scope: this
40158         });
40159     },
40160     
40161     /**
40162      * Method: moveGriddedTiles
40163      */
40164     moveGriddedTiles: function() {
40165         var buffer = this.buffer + 1;
40166         while(true) {
40167             var tlTile = this.grid[0][0];
40168             var tlViewPort = {
40169                 x: tlTile.position.x +
40170                     this.map.layerContainerOriginPx.x,
40171                 y: tlTile.position.y +
40172                     this.map.layerContainerOriginPx.y
40173             };
40174             var ratio = this.getServerResolution() / this.map.getResolution();
40175             var tileSize = {
40176                 w: Math.round(this.tileSize.w * ratio),
40177                 h: Math.round(this.tileSize.h * ratio)
40178             };
40179             if (tlViewPort.x > -tileSize.w * (buffer - 1)) {
40180                 this.shiftColumn(true, tileSize);
40181             } else if (tlViewPort.x < -tileSize.w * buffer) {
40182                 this.shiftColumn(false, tileSize);
40183             } else if (tlViewPort.y > -tileSize.h * (buffer - 1)) {
40184                 this.shiftRow(true, tileSize);
40185             } else if (tlViewPort.y < -tileSize.h * buffer) {
40186                 this.shiftRow(false, tileSize);
40187             } else {
40188                 break;
40189             }
40190         }
40191     },
40192
40193     /**
40194      * Method: shiftRow
40195      * Shifty grid work
40196      *
40197      * Parameters:
40198      * prepend - {Boolean} if true, prepend to beginning.
40199      *                          if false, then append to end
40200      * tileSize - {Object} rendered tile size; object with w and h properties
40201      */
40202     shiftRow: function(prepend, tileSize) {
40203         var grid = this.grid;
40204         var rowIndex = prepend ? 0 : (grid.length - 1);
40205         var sign = prepend ? -1 : 1;
40206         var rowSign = this.rowSign;
40207         var tileLayout = this.gridLayout;
40208         tileLayout.startrow += sign * rowSign;
40209
40210         var modelRow = grid[rowIndex];
40211         var row = grid[prepend ? 'pop' : 'shift']();
40212         for (var i=0, len=row.length; i<len; i++) {
40213             var tile = row[i];
40214             var position = modelRow[i].position.clone();
40215             position.y += tileSize.h * sign;
40216             tile.moveTo(this.getTileBoundsForGridIndex(rowIndex, i), position);
40217         }
40218         grid[prepend ? 'unshift' : 'push'](row);
40219     },
40220
40221     /**
40222      * Method: shiftColumn
40223      * Shift grid work in the other dimension
40224      *
40225      * Parameters:
40226      * prepend - {Boolean} if true, prepend to beginning.
40227      *                          if false, then append to end
40228      * tileSize - {Object} rendered tile size; object with w and h properties
40229      */
40230     shiftColumn: function(prepend, tileSize) {
40231         var grid = this.grid;
40232         var colIndex = prepend ? 0 : (grid[0].length - 1);
40233         var sign = prepend ? -1 : 1;
40234         var tileLayout = this.gridLayout;
40235         tileLayout.startcol += sign;
40236
40237         for (var i=0, len=grid.length; i<len; i++) {
40238             var row = grid[i];
40239             var position = row[colIndex].position.clone();
40240             var tile = row[prepend ? 'pop' : 'shift']();            
40241             position.x += tileSize.w * sign;
40242             tile.moveTo(this.getTileBoundsForGridIndex(i, colIndex), position);
40243             row[prepend ? 'unshift' : 'push'](tile);
40244         }
40245     },
40246
40247     /**
40248      * Method: removeExcessTiles
40249      * When the size of the map or the buffer changes, we may need to
40250      *     remove some excess rows and columns.
40251      * 
40252      * Parameters:
40253      * rows - {Integer} Maximum number of rows we want our grid to have.
40254      * columns - {Integer} Maximum number of columns we want our grid to have.
40255      */
40256     removeExcessTiles: function(rows, columns) {
40257         var i, l;
40258         
40259         // remove extra rows
40260         while (this.grid.length > rows) {
40261             var row = this.grid.pop();
40262             for (i=0, l=row.length; i<l; i++) {
40263                 var tile = row[i];
40264                 this.destroyTile(tile);
40265             }
40266         }
40267         
40268         // remove extra columns
40269         for (i=0, l=this.grid.length; i<l; i++) {
40270             while (this.grid[i].length > columns) {
40271                 var row = this.grid[i];
40272                 var tile = row.pop();
40273                 this.destroyTile(tile);
40274             }
40275         }
40276     },
40277
40278     /**
40279      * Method: onMapResize
40280      * For singleTile layers, this will set a new tile size according to the
40281      * dimensions of the map pane.
40282      */
40283     onMapResize: function() {
40284         if (this.singleTile) {
40285             this.clearGrid();
40286             this.setTileSize();
40287         }
40288     },
40289     
40290     /**
40291      * APIMethod: getTileBounds
40292      * Returns The tile bounds for a layer given a pixel location.
40293      *
40294      * Parameters:
40295      * viewPortPx - {<OpenLayers.Pixel>} The location in the viewport.
40296      *
40297      * Returns:
40298      * {<OpenLayers.Bounds>} Bounds of the tile at the given pixel location.
40299      */
40300     getTileBounds: function(viewPortPx) {
40301         var maxExtent = this.maxExtent;
40302         var resolution = this.getResolution();
40303         var tileMapWidth = resolution * this.tileSize.w;
40304         var tileMapHeight = resolution * this.tileSize.h;
40305         var mapPoint = this.getLonLatFromViewPortPx(viewPortPx);
40306         var tileLeft = maxExtent.left + (tileMapWidth *
40307                                          Math.floor((mapPoint.lon -
40308                                                      maxExtent.left) /
40309                                                     tileMapWidth));
40310         var tileBottom = maxExtent.bottom + (tileMapHeight *
40311                                              Math.floor((mapPoint.lat -
40312                                                          maxExtent.bottom) /
40313                                                         tileMapHeight));
40314         return new OpenLayers.Bounds(tileLeft, tileBottom,
40315                                      tileLeft + tileMapWidth,
40316                                      tileBottom + tileMapHeight);
40317     },
40318
40319     CLASS_NAME: "OpenLayers.Layer.Grid"
40320 });
40321 /* ======================================================================
40322     OpenLayers/Layer/XYZ.js
40323    ====================================================================== */
40324
40325 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
40326  * full list of contributors). Published under the 2-clause BSD license.
40327  * See license.txt in the OpenLayers distribution or repository for the
40328  * full text of the license. */
40329
40330 /**
40331  * @requires OpenLayers/Layer/Grid.js
40332  */
40333
40334 /** 
40335  * Class: OpenLayers.Layer.XYZ
40336  * The XYZ class is designed to make it easier for people who have tiles
40337  * arranged by a standard XYZ grid. 
40338  * 
40339  * Inherits from:
40340  *  - <OpenLayers.Layer.Grid>
40341  */
40342 OpenLayers.Layer.XYZ = OpenLayers.Class(OpenLayers.Layer.Grid, {
40343     
40344     /**
40345      * APIProperty: isBaseLayer
40346      * Default is true, as this is designed to be a base tile source. 
40347      */
40348     isBaseLayer: true,
40349     
40350     /**
40351      * APIProperty: sphericalMercator
40352      * Whether the tile extents should be set to the defaults for 
40353      *    spherical mercator. Useful for things like OpenStreetMap.
40354      *    Default is false, except for the OSM subclass.
40355      */
40356     sphericalMercator: false,
40357
40358     /**
40359      * APIProperty: zoomOffset
40360      * {Number} If your cache has more zoom levels than you want to provide
40361      *     access to with this layer, supply a zoomOffset.  This zoom offset
40362      *     is added to the current map zoom level to determine the level
40363      *     for a requested tile.  For example, if you supply a zoomOffset
40364      *     of 3, when the map is at the zoom 0, tiles will be requested from
40365      *     level 3 of your cache.  Default is 0 (assumes cache level and map
40366      *     zoom are equivalent).  Using <zoomOffset> is an alternative to
40367      *     setting <serverResolutions> if you only want to expose a subset
40368      *     of the server resolutions.
40369      */
40370     zoomOffset: 0,
40371     
40372     /**
40373      * APIProperty: serverResolutions
40374      * {Array} A list of all resolutions available on the server.  Only set this
40375      *     property if the map resolutions differ from the server. This
40376      *     property serves two purposes. (a) <serverResolutions> can include
40377      *     resolutions that the server supports and that you don't want to
40378      *     provide with this layer; you can also look at <zoomOffset>, which is
40379      *     an alternative to <serverResolutions> for that specific purpose.
40380      *     (b) The map can work with resolutions that aren't supported by
40381      *     the server, i.e. that aren't in <serverResolutions>. When the
40382      *     map is displayed in such a resolution data for the closest
40383      *     server-supported resolution is loaded and the layer div is
40384      *     stretched as necessary.
40385      */
40386     serverResolutions: null,
40387
40388     /**
40389      * Constructor: OpenLayers.Layer.XYZ
40390      *
40391      * Parameters:
40392      * name - {String}
40393      * url - {String}
40394      * options - {Object} Hashtable of extra options to tag onto the layer
40395      */
40396     initialize: function(name, url, options) {
40397         if (options && options.sphericalMercator || this.sphericalMercator) {
40398             options = OpenLayers.Util.extend({
40399                 projection: "EPSG:900913",
40400                 numZoomLevels: this.serverResolutions ?
40401                         this.serverResolutions.length : 19
40402             }, options);
40403         }
40404         OpenLayers.Layer.Grid.prototype.initialize.apply(this, [
40405             name || this.name, url || this.url, {}, options
40406         ]);
40407     },
40408     
40409     /**
40410      * APIMethod: clone
40411      * Create a clone of this layer
40412      *
40413      * Parameters:
40414      * obj - {Object} Is this ever used?
40415      * 
40416      * Returns:
40417      * {<OpenLayers.Layer.XYZ>} An exact clone of this OpenLayers.Layer.XYZ
40418      */
40419     clone: function (obj) {
40420         
40421         if (obj == null) {
40422             obj = new OpenLayers.Layer.XYZ(this.name,
40423                                             this.url,
40424                                             this.getOptions());
40425         }
40426
40427         //get all additions from superclasses
40428         obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
40429
40430         return obj;
40431     },    
40432
40433     /**
40434      * Method: getURL
40435      *
40436      * Parameters:
40437      * bounds - {<OpenLayers.Bounds>}
40438      *
40439      * Returns:
40440      * {String} A string with the layer's url and parameters and also the
40441      *          passed-in bounds and appropriate tile size specified as
40442      *          parameters
40443      */
40444     getURL: function (bounds) {
40445         var xyz = this.getXYZ(bounds);
40446         var url = this.url;
40447         if (OpenLayers.Util.isArray(url)) {
40448             var s = '' + xyz.x + xyz.y + xyz.z;
40449             url = this.selectUrl(s, url);
40450         }
40451         
40452         return OpenLayers.String.format(url, xyz);
40453     },
40454     
40455     /**
40456      * Method: getXYZ
40457      * Calculates x, y and z for the given bounds.
40458      *
40459      * Parameters:
40460      * bounds - {<OpenLayers.Bounds>}
40461      *
40462      * Returns:
40463      * {Object} - an object with x, y and z properties.
40464      */
40465     getXYZ: function(bounds) {
40466         var res = this.getServerResolution();
40467         var x = Math.round((bounds.left - this.tileOrigin.lon) /
40468             (res * this.tileSize.w));
40469         var y = Math.round((this.tileOrigin.lat - bounds.top) /
40470             (res * this.tileSize.h));
40471         var z = this.getServerZoom();
40472
40473         if (this.wrapDateLine) {
40474             var limit = Math.pow(2, z);
40475             x = ((x % limit) + limit) % limit;
40476         }
40477
40478         return {'x': x, 'y': y, 'z': z};
40479     },
40480     
40481     /* APIMethod: setMap
40482      * When the layer is added to a map, then we can fetch our origin 
40483      *    (if we don't have one.) 
40484      * 
40485      * Parameters:
40486      * map - {<OpenLayers.Map>}
40487      */
40488     setMap: function(map) {
40489         OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments);
40490         if (!this.tileOrigin) { 
40491             this.tileOrigin = new OpenLayers.LonLat(this.maxExtent.left,
40492                                                 this.maxExtent.top);
40493         }                                       
40494     },
40495
40496     CLASS_NAME: "OpenLayers.Layer.XYZ"
40497 });
40498 /* ======================================================================
40499     OpenLayers/Layer/Bing.js
40500    ====================================================================== */
40501
40502 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
40503  * full list of contributors). Published under the 2-clause BSD license.
40504  * See license.txt in the OpenLayers distribution or repository for the
40505  * full text of the license. */
40506
40507 /**
40508  * @requires OpenLayers/Layer/XYZ.js
40509  */
40510
40511 /** 
40512  * Class: OpenLayers.Layer.Bing
40513  * Bing layer using direct tile access as provided by Bing Maps REST Services.
40514  * See http://msdn.microsoft.com/en-us/library/ff701713.aspx for more
40515  * information. Note: Terms of Service compliant use requires the map to be
40516  * configured with an <OpenLayers.Control.Attribution> control and the
40517  * attribution placed on or near the map.
40518  * 
40519  * Inherits from:
40520  *  - <OpenLayers.Layer.XYZ>
40521  */
40522 OpenLayers.Layer.Bing = OpenLayers.Class(OpenLayers.Layer.XYZ, {
40523
40524     /**
40525      * Property: key
40526      * {String} API key for Bing maps, get your own key 
40527      *     at http://bingmapsportal.com/ .
40528      */
40529     key: null,
40530
40531     /**
40532      * Property: serverResolutions
40533      * {Array} the resolutions provided by the Bing servers.
40534      */
40535     serverResolutions: [
40536         156543.03390625, 78271.516953125, 39135.7584765625,
40537         19567.87923828125, 9783.939619140625, 4891.9698095703125,
40538         2445.9849047851562, 1222.9924523925781, 611.4962261962891,
40539         305.74811309814453, 152.87405654907226, 76.43702827453613,
40540         38.218514137268066, 19.109257068634033, 9.554628534317017,
40541         4.777314267158508, 2.388657133579254, 1.194328566789627,
40542         0.5971642833948135, 0.29858214169740677, 0.14929107084870338,
40543         0.07464553542435169, 0.03732276771218, 0.01866138385609
40544     ],
40545     
40546     /**
40547      * Property: attributionTemplate
40548      * {String}
40549      */
40550     attributionTemplate: '<span class="olBingAttribution ${type}">' +
40551          '<div><a target="_blank" href="http://www.bing.com/maps/">' +
40552          '<img src="${logo}" /></a></div>${copyrights}' +
40553          '<a style="white-space: nowrap" target="_blank" '+
40554          'href="http://www.microsoft.com/maps/product/terms.html">' +
40555          'Terms of Use</a></span>',
40556
40557     /**
40558      * Property: metadata
40559      * {Object} Metadata for this layer, as returned by the callback script
40560      */
40561     metadata: null,
40562
40563     /**
40564      * Property: protocolRegex
40565      * {RegExp} Regular expression to match and replace http: in bing urls
40566      */
40567     protocolRegex: /^http:/i,
40568     
40569     /**
40570      * APIProperty: type
40571      * {String} The layer identifier.  Any non-birdseye imageryType
40572      *     from http://msdn.microsoft.com/en-us/library/ff701716.aspx can be
40573      *     used.  Default is "Road".
40574      */
40575     type: "Road",
40576     
40577     /**
40578      * APIProperty: culture
40579      * {String} The culture identifier.  See http://msdn.microsoft.com/en-us/library/ff701709.aspx
40580      * for the definition and the possible values.  Default is "en-US".
40581      */
40582     culture: "en-US",
40583     
40584     /**
40585      * APIProperty: metadataParams
40586      * {Object} Optional url parameters for the Get Imagery Metadata request
40587      * as described here: http://msdn.microsoft.com/en-us/library/ff701716.aspx
40588      */
40589     metadataParams: null,
40590
40591     /** APIProperty: tileOptions
40592      *  {Object} optional configuration options for <OpenLayers.Tile> instances
40593      *  created by this Layer. Default is
40594      *
40595      *  (code)
40596      *  {crossOriginKeyword: 'anonymous'}
40597      *  (end)
40598      */
40599     tileOptions: null,
40600
40601     /** APIProperty: protocol
40602      *  {String} Protocol to use to fetch Imagery Metadata, tiles and bing logo
40603      *  Can be 'http:' 'https:' or ''
40604      *
40605      *  Warning: tiles may not be available under both HTTP and HTTPS protocols.
40606      *  Microsoft approved use of both HTTP and HTTPS urls for tiles. However
40607      *  this is undocumented and the Imagery Metadata API always returns HTTP
40608      *  urls.
40609      *
40610      *  Default is '', unless when executed from a file:/// uri, in which case
40611      *  it is 'http:'.
40612      */
40613     protocol: ~window.location.href.indexOf('http') ? '' : 'http:',
40614
40615     /**
40616      * Constructor: OpenLayers.Layer.Bing
40617      * Create a new Bing layer.
40618      *
40619      * Example:
40620      * (code)
40621      * var road = new OpenLayers.Layer.Bing({
40622      *     name: "My Bing Aerial Layer",
40623      *     type: "Aerial",
40624      *     key: "my-api-key-here",
40625      * });
40626      * (end)
40627      *
40628      * Parameters:
40629      * options - {Object} Configuration properties for the layer.
40630      *
40631      * Required configuration properties:
40632      * key - {String} Bing Maps API key for your application. Get one at
40633      *     http://bingmapsportal.com/.
40634      * type - {String} The layer identifier.  Any non-birdseye imageryType
40635      *     from http://msdn.microsoft.com/en-us/library/ff701716.aspx can be
40636      *     used.
40637      *
40638      * Any other documented layer properties can be provided in the config object.
40639      */
40640     initialize: function(options) {
40641         options = OpenLayers.Util.applyDefaults({
40642             sphericalMercator: true
40643         }, options);
40644         var name = options.name || "Bing " + (options.type || this.type);
40645         
40646         var newArgs = [name, null, options];
40647         OpenLayers.Layer.XYZ.prototype.initialize.apply(this, newArgs);
40648         this.tileOptions = OpenLayers.Util.extend({
40649             crossOriginKeyword: 'anonymous'
40650         }, this.options.tileOptions);
40651         this.loadMetadata(); 
40652     },
40653
40654     /**
40655      * Method: loadMetadata
40656      */
40657     loadMetadata: function() {
40658         this._callbackId = "_callback_" + this.id.replace(/\./g, "_");
40659         // link the processMetadata method to the global scope and bind it
40660         // to this instance
40661         window[this._callbackId] = OpenLayers.Function.bind(
40662             OpenLayers.Layer.Bing.processMetadata, this
40663         );
40664         var params = OpenLayers.Util.applyDefaults({
40665             key: this.key,
40666             jsonp: this._callbackId,
40667             include: "ImageryProviders"
40668         }, this.metadataParams);
40669         var url = this.protocol + "//dev.virtualearth.net/REST/v1/Imagery/Metadata/" +
40670             this.type + "?" + OpenLayers.Util.getParameterString(params);
40671         var script = document.createElement("script");
40672         script.type = "text/javascript";
40673         script.src = url;
40674         script.id = this._callbackId;
40675         document.getElementsByTagName("head")[0].appendChild(script);
40676     },
40677     
40678     /**
40679      * Method: initLayer
40680      *
40681      * Sets layer properties according to the metadata provided by the API
40682      */
40683     initLayer: function() {
40684         var res = this.metadata.resourceSets[0].resources[0];
40685         var url = res.imageUrl.replace("{quadkey}", "${quadkey}");
40686         url = url.replace("{culture}", this.culture);
40687         url = url.replace(this.protocolRegex, this.protocol);
40688         this.url = [];
40689         for (var i=0; i<res.imageUrlSubdomains.length; ++i) {
40690             this.url.push(url.replace("{subdomain}", res.imageUrlSubdomains[i]));
40691         }
40692         this.addOptions({
40693             maxResolution: Math.min(
40694                 this.serverResolutions[res.zoomMin],
40695                 this.maxResolution || Number.POSITIVE_INFINITY
40696             ),
40697             numZoomLevels: Math.min(
40698                 res.zoomMax + 1 - res.zoomMin, this.numZoomLevels
40699             )
40700         }, true);
40701         if (!this.isBaseLayer) {
40702             this.redraw();
40703         }
40704         this.updateAttribution();
40705     },
40706     
40707     /**
40708      * Method: getURL
40709      *
40710      * Parameters:
40711      * bounds - {<OpenLayers.Bounds>}
40712      */
40713     getURL: function(bounds) {
40714         if (!this.url) {
40715             return;
40716         }
40717         var xyz = this.getXYZ(bounds), x = xyz.x, y = xyz.y, z = xyz.z;
40718         var quadDigits = [];
40719         for (var i = z; i > 0; --i) {
40720             var digit = '0';
40721             var mask = 1 << (i - 1);
40722             if ((x & mask) != 0) {
40723                 digit++;
40724             }
40725             if ((y & mask) != 0) {
40726                 digit++;
40727                 digit++;
40728             }
40729             quadDigits.push(digit);
40730         }
40731         var quadKey = quadDigits.join("");
40732         var url = this.selectUrl('' + x + y + z, this.url);
40733
40734         return OpenLayers.String.format(url, {'quadkey': quadKey});
40735     },
40736     
40737     /**
40738      * Method: updateAttribution
40739      * Updates the attribution according to the requirements outlined in
40740      * http://gis.638310.n2.nabble.com/Bing-imagery-td5789168.html
40741      */
40742     updateAttribution: function() {
40743         var metadata = this.metadata;
40744         if (!metadata.resourceSets || !this.map || !this.map.center) {
40745             return;
40746         }
40747         var res = metadata.resourceSets[0].resources[0];
40748         var extent = this.map.getExtent().transform(
40749             this.map.getProjectionObject(),
40750             new OpenLayers.Projection("EPSG:4326")
40751         );
40752         var providers = res.imageryProviders || [],
40753             zoom = OpenLayers.Util.indexOf(this.serverResolutions,
40754                                            this.getServerResolution()),
40755             copyrights = "", provider, i, ii, j, jj, bbox, coverage;
40756         for (i=0,ii=providers.length; i<ii; ++i) {
40757             provider = providers[i];
40758             for (j=0,jj=provider.coverageAreas.length; j<jj; ++j) {
40759                 coverage = provider.coverageAreas[j];
40760                 // axis order provided is Y,X
40761                 bbox = OpenLayers.Bounds.fromArray(coverage.bbox, true);
40762                 if (extent.intersectsBounds(bbox) &&
40763                         zoom <= coverage.zoomMax && zoom >= coverage.zoomMin) {
40764                     copyrights += provider.attribution + " ";
40765                 }
40766             }
40767         }
40768         var logo = metadata.brandLogoUri.replace(this.protocolRegex, this.protocol);
40769         this.attribution = OpenLayers.String.format(this.attributionTemplate, {
40770             type: this.type.toLowerCase(),
40771             logo: logo,
40772             copyrights: copyrights
40773         });
40774         this.map && this.map.events.triggerEvent("changelayer", {
40775             layer: this,
40776             property: "attribution"
40777         });
40778     },
40779     
40780     /**
40781      * Method: setMap
40782      */
40783     setMap: function() {
40784         OpenLayers.Layer.XYZ.prototype.setMap.apply(this, arguments);
40785         this.map.events.register("moveend", this, this.updateAttribution);
40786     },
40787     
40788     /**
40789      * APIMethod: clone
40790      * 
40791      * Parameters:
40792      * obj - {Object}
40793      * 
40794      * Returns:
40795      * {<OpenLayers.Layer.Bing>} An exact clone of this <OpenLayers.Layer.Bing>
40796      */
40797     clone: function(obj) {
40798         if (obj == null) {
40799             obj = new OpenLayers.Layer.Bing(this.options);
40800         }
40801         //get all additions from superclasses
40802         obj = OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]);
40803         // copy/set any non-init, non-simple values here
40804         return obj;
40805     },
40806     
40807     /**
40808      * Method: destroy
40809      */
40810     destroy: function() {
40811         this.map &&
40812             this.map.events.unregister("moveend", this, this.updateAttribution);
40813         OpenLayers.Layer.XYZ.prototype.destroy.apply(this, arguments);
40814     },
40815     
40816     CLASS_NAME: "OpenLayers.Layer.Bing"
40817 });
40818
40819 /**
40820  * Function: OpenLayers.Layer.Bing.processMetadata
40821  * This function will be bound to an instance, linked to the global scope with
40822  * an id, and called by the JSONP script returned by the API.
40823  *
40824  * Parameters:
40825  * metadata - {Object} metadata as returned by the API
40826  */
40827 OpenLayers.Layer.Bing.processMetadata = function(metadata) {
40828     this.metadata = metadata;
40829     this.initLayer();
40830     var script = document.getElementById(this._callbackId);
40831     script.parentNode.removeChild(script);
40832     window[this._callbackId] = undefined; // cannot delete from window in IE
40833     delete this._callbackId;
40834 };
40835 /* ======================================================================
40836     OpenLayers/Layer/Markers.js
40837    ====================================================================== */
40838
40839 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
40840  * full list of contributors). Published under the 2-clause BSD license.
40841  * See license.txt in the OpenLayers distribution or repository for the
40842  * full text of the license. */
40843
40844
40845 /**
40846  * @requires OpenLayers/Layer.js
40847  */
40848
40849 /**
40850  * Class: OpenLayers.Layer.Markers
40851  * 
40852  * Inherits from:
40853  *  - <OpenLayers.Layer> 
40854  */
40855 OpenLayers.Layer.Markers = OpenLayers.Class(OpenLayers.Layer, {
40856     
40857     /** 
40858      * APIProperty: isBaseLayer 
40859      * {Boolean} Markers layer is never a base layer.  
40860      */
40861     isBaseLayer: false,
40862     
40863     /** 
40864      * APIProperty: markers 
40865      * {Array(<OpenLayers.Marker>)} internal marker list 
40866      */
40867     markers: null,
40868
40869
40870     /** 
40871      * Property: drawn 
40872      * {Boolean} internal state of drawing. This is a workaround for the fact
40873      * that the map does not call moveTo with a zoomChanged when the map is
40874      * first starting up. This lets us catch the case where we have *never*
40875      * drawn the layer, and draw it even if the zoom hasn't changed.
40876      */
40877     drawn: false,
40878     
40879     /**
40880      * Constructor: OpenLayers.Layer.Markers 
40881      * Create a Markers layer.
40882      *
40883      * Parameters:
40884      * name - {String} 
40885      * options - {Object} Hashtable of extra options to tag onto the layer
40886      */
40887     initialize: function(name, options) {
40888         OpenLayers.Layer.prototype.initialize.apply(this, arguments);
40889         this.markers = [];
40890     },
40891     
40892     /**
40893      * APIMethod: destroy 
40894      */
40895     destroy: function() {
40896         this.clearMarkers();
40897         this.markers = null;
40898         OpenLayers.Layer.prototype.destroy.apply(this, arguments);
40899     },
40900
40901     /**
40902      * APIMethod: setOpacity
40903      * Sets the opacity for all the markers.
40904      * 
40905      * Parameters:
40906      * opacity - {Float}
40907      */
40908     setOpacity: function(opacity) {
40909         if (opacity != this.opacity) {
40910             this.opacity = opacity;
40911             for (var i=0, len=this.markers.length; i<len; i++) {
40912                 this.markers[i].setOpacity(this.opacity);
40913             }
40914         }
40915     },
40916
40917     /** 
40918      * Method: moveTo
40919      *
40920      * Parameters:
40921      * bounds - {<OpenLayers.Bounds>} 
40922      * zoomChanged - {Boolean} 
40923      * dragging - {Boolean} 
40924      */
40925     moveTo:function(bounds, zoomChanged, dragging) {
40926         OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
40927
40928         if (zoomChanged || !this.drawn) {
40929             for(var i=0, len=this.markers.length; i<len; i++) {
40930                 this.drawMarker(this.markers[i]);
40931             }
40932             this.drawn = true;
40933         }
40934     },
40935
40936     /**
40937      * APIMethod: addMarker
40938      *
40939      * Parameters:
40940      * marker - {<OpenLayers.Marker>} 
40941      */
40942     addMarker: function(marker) {
40943         this.markers.push(marker);
40944
40945         if (this.opacity < 1) {
40946             marker.setOpacity(this.opacity);
40947         }
40948
40949         if (this.map && this.map.getExtent()) {
40950             marker.map = this.map;
40951             this.drawMarker(marker);
40952         }
40953     },
40954
40955     /**
40956      * APIMethod: removeMarker
40957      *
40958      * Parameters:
40959      * marker - {<OpenLayers.Marker>} 
40960      */
40961     removeMarker: function(marker) {
40962         if (this.markers && this.markers.length) {
40963             OpenLayers.Util.removeItem(this.markers, marker);
40964             marker.erase();
40965         }
40966     },
40967
40968     /**
40969      * Method: clearMarkers
40970      * This method removes all markers from a layer. The markers are not
40971      * destroyed by this function, but are removed from the list of markers.
40972      */
40973     clearMarkers: function() {
40974         if (this.markers != null) {
40975             while(this.markers.length > 0) {
40976                 this.removeMarker(this.markers[0]);
40977             }
40978         }
40979     },
40980
40981     /** 
40982      * Method: drawMarker
40983      * Calculate the pixel location for the marker, create it, and 
40984      *    add it to the layer's div
40985      *
40986      * Parameters:
40987      * marker - {<OpenLayers.Marker>} 
40988      */
40989     drawMarker: function(marker) {
40990         var px = this.map.getLayerPxFromLonLat(marker.lonlat);
40991         if (px == null) {
40992             marker.display(false);
40993         } else {
40994             if (!marker.isDrawn()) {
40995                 var markerImg = marker.draw(px);
40996                 this.div.appendChild(markerImg);
40997             } else if(marker.icon) {
40998                 marker.icon.moveTo(px);
40999             }
41000         }
41001     },
41002     
41003     /** 
41004      * APIMethod: getDataExtent
41005      * Calculates the max extent which includes all of the markers.
41006      * 
41007      * Returns:
41008      * {<OpenLayers.Bounds>}
41009      */
41010     getDataExtent: function () {
41011         var maxExtent = null;
41012         
41013         if ( this.markers && (this.markers.length > 0)) {
41014             var maxExtent = new OpenLayers.Bounds();
41015             for(var i=0, len=this.markers.length; i<len; i++) {
41016                 var marker = this.markers[i];
41017                 maxExtent.extend(marker.lonlat);
41018             }
41019         }
41020
41021         return maxExtent;
41022     },
41023
41024     CLASS_NAME: "OpenLayers.Layer.Markers"
41025 });
41026 /* ======================================================================
41027     OpenLayers/Layer/OSM.js
41028    ====================================================================== */
41029
41030 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
41031  * full list of contributors). Published under the 2-clause BSD license.
41032  * See license.txt in the OpenLayers distribution or repository for the
41033  * full text of the license. */
41034
41035 /**
41036  * @requires OpenLayers/Layer/XYZ.js
41037  */
41038
41039 /**
41040  * Class: OpenLayers.Layer.OSM
41041  * This layer allows accessing OpenStreetMap tiles. By default the OpenStreetMap
41042  *    hosted tile.openstreetmap.org Mapnik tileset is used. If you wish to use
41043  *    a different layer instead, you need to provide a different
41044  *    URL to the constructor. Here's an example for using OpenCycleMap:
41045  * 
41046  * (code)
41047  *     new OpenLayers.Layer.OSM("OpenCycleMap", 
41048  *       ["http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
41049  *        "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
41050  *        "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png"]); 
41051  * (end)
41052  *
41053  * Inherits from:
41054  *  - <OpenLayers.Layer.XYZ>
41055  */
41056 OpenLayers.Layer.OSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
41057
41058     /**
41059      * APIProperty: name
41060      * {String} The layer name. Defaults to "OpenStreetMap" if the first
41061      * argument to the constructor is null or undefined.
41062      */
41063     name: "OpenStreetMap",
41064
41065     /**
41066      * APIProperty: url
41067      * {String} The tileset URL scheme. Defaults to (protocol relative url):
41068      *  //[a|b|c].tile.openstreetmap.org/${z}/${x}/${y}.png 
41069      * (the official OSM tileset) if the second argument to the constructor
41070      * is null or undefined. To use another tileset you can have something
41071      * like this:
41072      * (code)
41073      *     new OpenLayers.Layer.OSM("OpenCycleMap", 
41074      *       ["http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
41075      *        "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
41076      *        "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png"]); 
41077      * (end)
41078      */
41079     url: [
41080         '//a.tile.openstreetmap.org/${z}/${x}/${y}.png',
41081         '//b.tile.openstreetmap.org/${z}/${x}/${y}.png',
41082         '//c.tile.openstreetmap.org/${z}/${x}/${y}.png'
41083     ],
41084
41085     /**
41086      * Property: attribution
41087      * {String} The layer attribution.
41088      */
41089     attribution: "&copy; <a href='//www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors",
41090
41091     /**
41092      * Property: sphericalMercator
41093      * {Boolean}
41094      */
41095     sphericalMercator: true,
41096
41097     /**
41098      * Property: wrapDateLine
41099      * {Boolean}
41100      */
41101     wrapDateLine: true,
41102
41103     /** APIProperty: tileOptions
41104      *  {Object} optional configuration options for <OpenLayers.Tile> instances
41105      *  created by this Layer. Default is
41106      *
41107      *  (code)
41108      *  {crossOriginKeyword: 'anonymous'}
41109      *  (end)
41110      *
41111      *  When using OSM tilesets other than the default ones, it may be
41112      *  necessary to set this to
41113      *
41114      *  (code)
41115      *  {crossOriginKeyword: null}
41116      *  (end)
41117      *
41118      *  if the server does not send Access-Control-Allow-Origin headers.
41119      */
41120     tileOptions: null,
41121
41122     /**
41123      * Constructor: OpenLayers.Layer.OSM
41124      *
41125      * Parameters:
41126      * name - {String} The layer name.
41127      * url - {String} The tileset URL scheme.
41128      * options - {Object} Configuration options for the layer. Any inherited
41129      *     layer option can be set in this object (e.g.
41130      *     <OpenLayers.Layer.Grid.buffer>).
41131      */
41132     initialize: function(name, url, options) {
41133         OpenLayers.Layer.XYZ.prototype.initialize.apply(this, arguments);
41134         this.tileOptions = OpenLayers.Util.extend({
41135             crossOriginKeyword: 'anonymous'
41136         }, this.options && this.options.tileOptions);
41137     },
41138
41139     /**
41140      * Method: clone
41141      */
41142     clone: function(obj) {
41143         if (obj == null) {
41144             obj = new OpenLayers.Layer.OSM(
41145                 this.name, this.url, this.getOptions());
41146         }
41147         obj = OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]);
41148         return obj;
41149     },
41150
41151     CLASS_NAME: "OpenLayers.Layer.OSM"
41152 });
41153 /* ======================================================================
41154     OpenLayers/Layer/SphericalMercator.js
41155    ====================================================================== */
41156
41157 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
41158  * full list of contributors). Published under the 2-clause BSD license.
41159  * See license.txt in the OpenLayers distribution or repository for the
41160  * full text of the license. */
41161
41162 /**
41163  * @requires OpenLayers/Layer.js
41164  * @requires OpenLayers/Projection.js
41165  */
41166
41167 /**
41168  * Class: OpenLayers.Layer.SphericalMercator
41169  * A mixin for layers that wraps up the pieces necessary to have a coordinate
41170  *     conversion for working with commercial APIs which use a spherical
41171  *     mercator projection.  Using this layer as a base layer, additional
41172  *     layers can be used as overlays if they are in the same projection.
41173  *
41174  * A layer is given properties of this object by setting the sphericalMercator
41175  *     property to true.
41176  *
41177  * More projection information:
41178  *  - http://spatialreference.org/ref/user/google-projection/
41179  *
41180  * Proj4 Text:
41181  *     +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0
41182  *     +k=1.0 +units=m +nadgrids=@null +no_defs
41183  *
41184  * WKT:
41185  *     900913=PROJCS["WGS84 / Simple Mercator", GEOGCS["WGS 84",
41186  *     DATUM["WGS_1984", SPHEROID["WGS_1984", 6378137.0, 298.257223563]], 
41187  *     PRIMEM["Greenwich", 0.0], UNIT["degree", 0.017453292519943295], 
41188  *     AXIS["Longitude", EAST], AXIS["Latitude", NORTH]],
41189  *     PROJECTION["Mercator_1SP_Google"], 
41190  *     PARAMETER["latitude_of_origin", 0.0], PARAMETER["central_meridian", 0.0], 
41191  *     PARAMETER["scale_factor", 1.0], PARAMETER["false_easting", 0.0], 
41192  *     PARAMETER["false_northing", 0.0], UNIT["m", 1.0], AXIS["x", EAST],
41193  *     AXIS["y", NORTH], AUTHORITY["EPSG","900913"]]
41194  */
41195 OpenLayers.Layer.SphericalMercator = {
41196
41197     /**
41198      * Method: getExtent
41199      * Get the map's extent.
41200      *
41201      * Returns:
41202      * {<OpenLayers.Bounds>} The map extent.
41203      */
41204     getExtent: function() {
41205         var extent = null;
41206         if (this.sphericalMercator) {
41207             extent = this.map.calculateBounds();
41208         } else {
41209             extent = OpenLayers.Layer.FixedZoomLevels.prototype.getExtent.apply(this);
41210         }
41211         return extent;
41212     },
41213
41214     /**
41215      * Method: getLonLatFromViewPortPx
41216      * Get a map location from a pixel location
41217      * 
41218      * Parameters:
41219      * viewPortPx - {<OpenLayers.Pixel>}
41220      *
41221      * Returns:
41222      *  {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in view
41223      *  port OpenLayers.Pixel, translated into lon/lat by map lib
41224      *  If the map lib is not loaded or not centered, returns null
41225      */
41226     getLonLatFromViewPortPx: function (viewPortPx) {
41227         return OpenLayers.Layer.prototype.getLonLatFromViewPortPx.apply(this, arguments);
41228     },
41229     
41230     /**
41231      * Method: getViewPortPxFromLonLat
41232      * Get a pixel location from a map location
41233      *
41234      * Parameters:
41235      * lonlat - {<OpenLayers.LonLat>}
41236      *
41237      * Returns:
41238      * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in
41239      * OpenLayers.LonLat, translated into view port pixels by map lib
41240      * If map lib is not loaded or not centered, returns null
41241      */
41242     getViewPortPxFromLonLat: function (lonlat) {
41243         return OpenLayers.Layer.prototype.getViewPortPxFromLonLat.apply(this, arguments);
41244     },
41245
41246     /** 
41247      * Method: initMercatorParameters 
41248      * Set up the mercator parameters on the layer: resolutions,
41249      *     projection, units.
41250      */
41251     initMercatorParameters: function() {
41252         // set up properties for Mercator - assume EPSG:900913
41253         this.RESOLUTIONS = [];
41254         var maxResolution = 156543.03390625;
41255         for(var zoom=0; zoom<=this.MAX_ZOOM_LEVEL; ++zoom) {
41256             this.RESOLUTIONS[zoom] = maxResolution / Math.pow(2, zoom);
41257         }
41258         this.units = "m";
41259         this.projection = this.projection || "EPSG:900913";
41260     },
41261
41262     /**
41263      * APIMethod: forwardMercator
41264      * Given a lon,lat in EPSG:4326, return a point in Spherical Mercator.
41265      *
41266      * Parameters:
41267      * lon - {float} 
41268      * lat - {float}
41269      * 
41270      * Returns:
41271      * {<OpenLayers.LonLat>} The coordinates transformed to Mercator.
41272      */
41273     forwardMercator: (function() {
41274         var gg = new OpenLayers.Projection("EPSG:4326");
41275         var sm = new OpenLayers.Projection("EPSG:900913");
41276         return function(lon, lat) {
41277             var point = OpenLayers.Projection.transform({x: lon, y: lat}, gg, sm);
41278             return new OpenLayers.LonLat(point.x, point.y);
41279         };
41280     })(),
41281
41282     /**
41283      * APIMethod: inverseMercator
41284      * Given a x,y in Spherical Mercator, return a point in EPSG:4326.
41285      *
41286      * Parameters:
41287      * x - {float} A map x in Spherical Mercator.
41288      * y - {float} A map y in Spherical Mercator.
41289      * 
41290      * Returns:
41291      * {<OpenLayers.LonLat>} The coordinates transformed to EPSG:4326.
41292      */
41293     inverseMercator: (function() {
41294         var gg = new OpenLayers.Projection("EPSG:4326");
41295         var sm = new OpenLayers.Projection("EPSG:900913");
41296         return function(x, y) {
41297             var point = OpenLayers.Projection.transform({x: x, y: y}, sm, gg);
41298             return new OpenLayers.LonLat(point.x, point.y);
41299         };
41300     })()
41301
41302 };
41303 /* ======================================================================
41304     OpenLayers/Layer/EventPane.js
41305    ====================================================================== */
41306
41307 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
41308  * full list of contributors). Published under the 2-clause BSD license.
41309  * See license.txt in the OpenLayers distribution or repository for the
41310  * full text of the license. */
41311
41312
41313 /**
41314  * @requires OpenLayers/Layer.js
41315  * @requires OpenLayers/Util.js
41316  */
41317
41318 /**
41319  * Class: OpenLayers.Layer.EventPane
41320  * Base class for 3rd party layers, providing a DOM element which isolates
41321  * the 3rd-party layer from mouse events.
41322  * Only used by Google layers.
41323  *
41324  * Automatically instantiated by the Google constructor, and not usually instantiated directly.
41325  *
41326  * Create a new event pane layer with the
41327  * <OpenLayers.Layer.EventPane> constructor.
41328  * 
41329  * Inherits from:
41330  *  - <OpenLayers.Layer>
41331  */
41332 OpenLayers.Layer.EventPane = OpenLayers.Class(OpenLayers.Layer, {
41333     
41334     /**
41335      * APIProperty: smoothDragPan
41336      * {Boolean} smoothDragPan determines whether non-public/internal API
41337      *     methods are used for better performance while dragging EventPane 
41338      *     layers. When not in sphericalMercator mode, the smoother dragging 
41339      *     doesn't actually move north/south directly with the number of 
41340      *     pixels moved, resulting in a slight offset when you drag your mouse 
41341      *     north south with this option on. If this visual disparity bothers 
41342      *     you, you should turn this option off, or use spherical mercator. 
41343      *     Default is on.
41344      */
41345     smoothDragPan: true,
41346
41347     /**
41348      * Property: isBaseLayer
41349      * {Boolean} EventPaned layers are always base layers, by necessity.
41350      */ 
41351     isBaseLayer: true,
41352
41353     /**
41354      * APIProperty: isFixed
41355      * {Boolean} EventPaned layers are fixed by default.
41356      */ 
41357     isFixed: true,
41358
41359     /**
41360      * Property: pane
41361      * {DOMElement} A reference to the element that controls the events.
41362      */
41363     pane: null,
41364
41365
41366     /**
41367      * Property: mapObject
41368      * {Object} This is the object which will be used to load the 3rd party library
41369      * in the case of the google layer, this will be of type GMap, 
41370      * in the case of the ve layer, this will be of type VEMap
41371      */ 
41372     mapObject: null,
41373
41374
41375     /**
41376      * Constructor: OpenLayers.Layer.EventPane
41377      * Create a new event pane layer
41378      *
41379      * Parameters:
41380      * name - {String}
41381      * options - {Object} Hashtable of extra options to tag onto the layer
41382      */
41383     initialize: function(name, options) {
41384         OpenLayers.Layer.prototype.initialize.apply(this, arguments);
41385         if (this.pane == null) {
41386             this.pane = OpenLayers.Util.createDiv(this.div.id + "_EventPane");
41387         }
41388     },
41389     
41390     /**
41391      * APIMethod: destroy
41392      * Deconstruct this layer.
41393      */
41394     destroy: function() {
41395         this.mapObject = null;
41396         this.pane = null;
41397         OpenLayers.Layer.prototype.destroy.apply(this, arguments); 
41398     },
41399
41400     
41401     /**
41402      * Method: setMap
41403      * Set the map property for the layer. This is done through an accessor
41404      * so that subclasses can override this and take special action once 
41405      * they have their map variable set. 
41406      *
41407      * Parameters:
41408      * map - {<OpenLayers.Map>}
41409      */
41410     setMap: function(map) {
41411         OpenLayers.Layer.prototype.setMap.apply(this, arguments);
41412         
41413         this.pane.style.zIndex = parseInt(this.div.style.zIndex) + 1;
41414         this.pane.style.display = this.div.style.display;
41415         this.pane.style.width="100%";
41416         this.pane.style.height="100%";
41417         if (OpenLayers.BROWSER_NAME == "msie") {
41418             this.pane.style.background = 
41419                 "url(" + OpenLayers.Util.getImageLocation("blank.gif") + ")";
41420         }
41421
41422         if (this.isFixed) {
41423             this.map.viewPortDiv.appendChild(this.pane);
41424         } else {
41425             this.map.layerContainerDiv.appendChild(this.pane);
41426         }
41427
41428         // once our layer has been added to the map, we can load it
41429         this.loadMapObject();
41430     
41431         // if map didn't load, display warning
41432         if (this.mapObject == null) {
41433             this.loadWarningMessage();
41434         }
41435
41436         this.map.events.register('zoomstart', this, this.onZoomStart);
41437     },
41438
41439     /**
41440      * APIMethod: removeMap
41441      * On being removed from the map, we'll like to remove the invisible 'pane'
41442      *     div that we added to it on creation. 
41443      * 
41444      * Parameters:
41445      * map - {<OpenLayers.Map>}
41446      */
41447     removeMap: function(map) {
41448         this.map.events.unregister('zoomstart', this, this.onZoomStart);
41449
41450         if (this.pane && this.pane.parentNode) {
41451             this.pane.parentNode.removeChild(this.pane);
41452         }
41453         OpenLayers.Layer.prototype.removeMap.apply(this, arguments);
41454     },
41455
41456     /**
41457      * Method: onZoomStart
41458      *
41459      * Parameters:
41460      * evt - zoomstart event object with center and zoom properties.
41461      */
41462     onZoomStart: function(evt) {
41463         if (this.mapObject != null) {
41464             var center = this.getMapObjectLonLatFromOLLonLat(evt.center);
41465             var zoom = this.getMapObjectZoomFromOLZoom(evt.zoom);
41466             this.setMapObjectCenter(center, zoom, false);
41467         }
41468     },
41469   
41470     /**
41471      * Method: loadWarningMessage
41472      * If we can't load the map lib, then display an error message to the 
41473      *     user and tell them where to go for help.
41474      * 
41475      *     This function sets up the layout for the warning message. Each 3rd
41476      *     party layer must implement its own getWarningHTML() function to 
41477      *     provide the actual warning message.
41478      */
41479     loadWarningMessage:function() {
41480
41481         this.div.style.backgroundColor = "darkblue";
41482
41483         var viewSize = this.map.getSize();
41484         
41485         var msgW = Math.min(viewSize.w, 300);
41486         var msgH = Math.min(viewSize.h, 200);
41487         var size = new OpenLayers.Size(msgW, msgH);
41488
41489         var centerPx = new OpenLayers.Pixel(viewSize.w/2, viewSize.h/2);
41490
41491         var topLeft = centerPx.add(-size.w/2, -size.h/2);            
41492
41493         var div = OpenLayers.Util.createDiv(this.name + "_warning", 
41494                                             topLeft, 
41495                                             size,
41496                                             null,
41497                                             null,
41498                                             null,
41499                                             "auto");
41500
41501         div.style.padding = "7px";
41502         div.style.backgroundColor = "yellow";
41503
41504         div.innerHTML = this.getWarningHTML();
41505         this.div.appendChild(div);
41506     },
41507   
41508     /** 
41509      * Method: getWarningHTML
41510      * To be implemented by subclasses.
41511      * 
41512      * Returns:
41513      * {String} String with information on why layer is broken, how to get
41514      *          it working.
41515      */
41516     getWarningHTML:function() {
41517         //should be implemented by subclasses
41518         return "";
41519     },
41520   
41521     /**
41522      * Method: display
41523      * Set the display on the pane
41524      *
41525      * Parameters:
41526      * display - {Boolean}
41527      */
41528     display: function(display) {
41529         OpenLayers.Layer.prototype.display.apply(this, arguments);
41530         this.pane.style.display = this.div.style.display;
41531     },
41532   
41533     /**
41534      * Method: setZIndex
41535      * Set the z-index order for the pane.
41536      * 
41537      * Parameters:
41538      * zIndex - {int}
41539      */
41540     setZIndex: function (zIndex) {
41541         OpenLayers.Layer.prototype.setZIndex.apply(this, arguments);
41542         this.pane.style.zIndex = parseInt(this.div.style.zIndex) + 1;
41543     },
41544     
41545     /**
41546      * Method: moveByPx
41547      * Move the layer based on pixel vector. To be implemented by subclasses.
41548      *
41549      * Parameters:
41550      * dx - {Number} The x coord of the displacement vector.
41551      * dy - {Number} The y coord of the displacement vector.
41552      */
41553     moveByPx: function(dx, dy) {
41554         OpenLayers.Layer.prototype.moveByPx.apply(this, arguments);
41555         
41556         if (this.dragPanMapObject) {
41557             this.dragPanMapObject(dx, -dy);
41558         } else {
41559             this.moveTo(this.map.getCachedCenter());
41560         }
41561     },
41562
41563     /**
41564      * Method: moveTo
41565      * Handle calls to move the layer.
41566      * 
41567      * Parameters:
41568      * bounds - {<OpenLayers.Bounds>}
41569      * zoomChanged - {Boolean}
41570      * dragging - {Boolean}
41571      */
41572     moveTo:function(bounds, zoomChanged, dragging) {
41573         OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
41574
41575         if (this.mapObject != null) {
41576
41577             var newCenter = this.map.getCenter();
41578             var newZoom = this.map.getZoom();
41579
41580             if (newCenter != null) {
41581
41582                 var moOldCenter = this.getMapObjectCenter();
41583                 var oldCenter = this.getOLLonLatFromMapObjectLonLat(moOldCenter);
41584
41585                 var moOldZoom = this.getMapObjectZoom();
41586                 var oldZoom= this.getOLZoomFromMapObjectZoom(moOldZoom);
41587
41588                 if (!(newCenter.equals(oldCenter)) || newZoom != oldZoom) {
41589
41590                     if (!zoomChanged && oldCenter && this.dragPanMapObject && 
41591                         this.smoothDragPan) {
41592                         var oldPx = this.map.getViewPortPxFromLonLat(oldCenter);
41593                         var newPx = this.map.getViewPortPxFromLonLat(newCenter);
41594                         this.dragPanMapObject(newPx.x-oldPx.x, oldPx.y-newPx.y);
41595                     } else {
41596                         var center = this.getMapObjectLonLatFromOLLonLat(newCenter);
41597                         var zoom = this.getMapObjectZoomFromOLZoom(newZoom);
41598                         this.setMapObjectCenter(center, zoom, dragging);
41599                     }
41600                 }
41601             }
41602         }
41603     },
41604
41605
41606   /********************************************************/
41607   /*                                                      */
41608   /*                 Baselayer Functions                  */
41609   /*                                                      */
41610   /********************************************************/
41611
41612     /**
41613      * Method: getLonLatFromViewPortPx
41614      * Get a map location from a pixel location
41615      * 
41616      * Parameters:
41617      * viewPortPx - {<OpenLayers.Pixel>}
41618      *
41619      * Returns:
41620      *  {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in view
41621      *  port OpenLayers.Pixel, translated into lon/lat by map lib
41622      *  If the map lib is not loaded or not centered, returns null
41623      */
41624     getLonLatFromViewPortPx: function (viewPortPx) {
41625         var lonlat = null;
41626         if ( (this.mapObject != null) && 
41627              (this.getMapObjectCenter() != null) ) {
41628             var moPixel = this.getMapObjectPixelFromOLPixel(viewPortPx);
41629             var moLonLat = this.getMapObjectLonLatFromMapObjectPixel(moPixel);
41630             lonlat = this.getOLLonLatFromMapObjectLonLat(moLonLat);
41631         }
41632         return lonlat;
41633     },
41634
41635  
41636     /**
41637      * Method: getViewPortPxFromLonLat
41638      * Get a pixel location from a map location
41639      *
41640      * Parameters:
41641      * lonlat - {<OpenLayers.LonLat>}
41642      *
41643      * Returns:
41644      * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in
41645      * OpenLayers.LonLat, translated into view port pixels by map lib
41646      * If map lib is not loaded or not centered, returns null
41647      */
41648     getViewPortPxFromLonLat: function (lonlat) {
41649         var viewPortPx = null;
41650         if ( (this.mapObject != null) && 
41651              (this.getMapObjectCenter() != null) ) {
41652
41653             var moLonLat = this.getMapObjectLonLatFromOLLonLat(lonlat);
41654             var moPixel = this.getMapObjectPixelFromMapObjectLonLat(moLonLat);
41655         
41656             viewPortPx = this.getOLPixelFromMapObjectPixel(moPixel);
41657         }
41658         return viewPortPx;
41659     },
41660
41661   /********************************************************/
41662   /*                                                      */
41663   /*               Translation Functions                  */
41664   /*                                                      */
41665   /*   The following functions translate Map Object and   */
41666   /*            OL formats for Pixel, LonLat              */
41667   /*                                                      */
41668   /********************************************************/
41669
41670   //
41671   // TRANSLATION: MapObject LatLng <-> OpenLayers.LonLat
41672   //
41673
41674     /**
41675      * Method: getOLLonLatFromMapObjectLonLat
41676      * Get an OL style map location from a 3rd party style map location
41677      *
41678      * Parameters
41679      * moLonLat - {Object}
41680      * 
41681      * Returns:
41682      * {<OpenLayers.LonLat>} An OpenLayers.LonLat, translated from the passed in 
41683      *          MapObject LonLat
41684      *          Returns null if null value is passed in
41685      */
41686     getOLLonLatFromMapObjectLonLat: function(moLonLat) {
41687         var olLonLat = null;
41688         if (moLonLat != null) {
41689             var lon = this.getLongitudeFromMapObjectLonLat(moLonLat);
41690             var lat = this.getLatitudeFromMapObjectLonLat(moLonLat);
41691             olLonLat = new OpenLayers.LonLat(lon, lat);
41692         }
41693         return olLonLat;
41694     },
41695
41696     /**
41697      * Method: getMapObjectLonLatFromOLLonLat
41698      * Get a 3rd party map location from an OL map location.
41699      *
41700      * Parameters:
41701      * olLonLat - {<OpenLayers.LonLat>}
41702      * 
41703      * Returns:
41704      * {Object} A MapObject LonLat, translated from the passed in 
41705      *          OpenLayers.LonLat
41706      *          Returns null if null value is passed in
41707      */
41708     getMapObjectLonLatFromOLLonLat: function(olLonLat) {
41709         var moLatLng = null;
41710         if (olLonLat != null) {
41711             moLatLng = this.getMapObjectLonLatFromLonLat(olLonLat.lon,
41712                                                          olLonLat.lat);
41713         }
41714         return moLatLng;
41715     },
41716
41717
41718   //
41719   // TRANSLATION: MapObject Pixel <-> OpenLayers.Pixel
41720   //
41721
41722     /**
41723      * Method: getOLPixelFromMapObjectPixel
41724      * Get an OL pixel location from a 3rd party pixel location.
41725      *
41726      * Parameters:
41727      * moPixel - {Object}
41728      * 
41729      * Returns:
41730      * {<OpenLayers.Pixel>} An OpenLayers.Pixel, translated from the passed in 
41731      *          MapObject Pixel
41732      *          Returns null if null value is passed in
41733      */
41734     getOLPixelFromMapObjectPixel: function(moPixel) {
41735         var olPixel = null;
41736         if (moPixel != null) {
41737             var x = this.getXFromMapObjectPixel(moPixel);
41738             var y = this.getYFromMapObjectPixel(moPixel);
41739             olPixel = new OpenLayers.Pixel(x, y);
41740         }
41741         return olPixel;
41742     },
41743
41744     /**
41745      * Method: getMapObjectPixelFromOLPixel
41746      * Get a 3rd party pixel location from an OL pixel location
41747      *
41748      * Parameters:
41749      * olPixel - {<OpenLayers.Pixel>}
41750      * 
41751      * Returns:
41752      * {Object} A MapObject Pixel, translated from the passed in 
41753      *          OpenLayers.Pixel
41754      *          Returns null if null value is passed in
41755      */
41756     getMapObjectPixelFromOLPixel: function(olPixel) {
41757         var moPixel = null;
41758         if (olPixel != null) {
41759             moPixel = this.getMapObjectPixelFromXY(olPixel.x, olPixel.y);
41760         }
41761         return moPixel;
41762     },
41763
41764     CLASS_NAME: "OpenLayers.Layer.EventPane"
41765 });
41766 /* ======================================================================
41767     OpenLayers/Layer/FixedZoomLevels.js
41768    ====================================================================== */
41769
41770 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
41771  * full list of contributors). Published under the 2-clause BSD license.
41772  * See license.txt in the OpenLayers distribution or repository for the
41773  * full text of the license. */
41774
41775 /**
41776  * @requires OpenLayers/Layer.js
41777  */
41778
41779 /**
41780  * Class: OpenLayers.Layer.FixedZoomLevels
41781  *   Some Layers will already have established zoom levels (like google 
41782  *    or ve). Instead of trying to determine them and populate a resolutions[]
41783  *    Array with those values, we will hijack the resolution functionality
41784  *    here.
41785  * 
41786  *   When you subclass FixedZoomLevels: 
41787  * 
41788  *   The initResolutions() call gets nullified, meaning no resolutions[] array 
41789  *    is set up. Which would be a big problem getResolution() in Layer, since 
41790  *    it merely takes map.zoom and indexes into resolutions[]... but....
41791  * 
41792  *   The getResolution() call is also overridden. Instead of using the 
41793  *    resolutions[] array, we simply calculate the current resolution based
41794  *    on the current extent and the current map size. But how will we be able
41795  *    to calculate the current extent without knowing the resolution...?
41796  *  
41797  *   The getExtent() function is also overridden. Instead of calculating extent
41798  *    based on the center point and the current resolution, we instead 
41799  *    calculate the extent by getting the lonlats at the top-left and 
41800  *    bottom-right by using the getLonLatFromViewPortPx() translation function,
41801  *    taken from the pixel locations (0,0) and the size of the map. But how 
41802  *    will we be able to do lonlat-px translation without resolution....?
41803  * 
41804  *   The getZoomForResolution() method is overridden. Instead of indexing into
41805  *    the resolutions[] array, we call OpenLayers.Layer.getExent(), passing in
41806  *    the desired resolution. With this extent, we then call getZoomForExtent() 
41807  * 
41808  * 
41809  *   Whenever you implement a layer using OpenLayers.Layer.FixedZoomLevels, 
41810  *    it is your responsibility to provide the following three functions:
41811  * 
41812  *   - getLonLatFromViewPortPx
41813  *   - getViewPortPxFromLonLat
41814  *   - getZoomForExtent
41815  * 
41816  *  ...those three functions should generally be provided by any reasonable 
41817  *  API that you might be working from.
41818  *
41819  */
41820 OpenLayers.Layer.FixedZoomLevels = OpenLayers.Class({
41821       
41822   /********************************************************/
41823   /*                                                      */
41824   /*                 Baselayer Functions                  */
41825   /*                                                      */
41826   /*    The following functions must all be implemented   */
41827   /*                  by all base layers                  */
41828   /*                                                      */
41829   /********************************************************/
41830     
41831     /**
41832      * Constructor: OpenLayers.Layer.FixedZoomLevels
41833      * Create a new fixed zoom levels layer.
41834      */
41835     initialize: function() {
41836         //this class is only just to add the following functions... 
41837         // nothing to actually do here... but it is probably a good
41838         // idea to have layers that use these functions call this 
41839         // inititalize() anyways, in case at some point we decide we 
41840         // do want to put some functionality or state in here. 
41841     },
41842     
41843     /**
41844      * Method: initResolutions
41845      * Populate the resolutions array
41846      */
41847     initResolutions: function() {
41848
41849         var props = ['minZoomLevel', 'maxZoomLevel', 'numZoomLevels'];
41850           
41851         for(var i=0, len=props.length; i<len; i++) {
41852             var property = props[i];
41853             this[property] = (this.options[property] != null)  
41854                                      ? this.options[property] 
41855                                      : this.map[property];
41856         }
41857
41858         if ( (this.minZoomLevel == null) ||
41859              (this.minZoomLevel < this.MIN_ZOOM_LEVEL) ){
41860             this.minZoomLevel = this.MIN_ZOOM_LEVEL;
41861         }        
41862
41863         //
41864         // At this point, we know what the minimum desired zoom level is, and
41865         //  we must calculate the total number of zoom levels. 
41866         //  
41867         //  Because we allow for the setting of either the 'numZoomLevels'
41868         //   or the 'maxZoomLevel' properties... on either the layer or the  
41869         //   map, we have to define some rules to see which we take into
41870         //   account first in this calculation. 
41871         //
41872         // The following is the precedence list for these properties:
41873         // 
41874         // (1) numZoomLevels set on layer
41875         // (2) maxZoomLevel set on layer
41876         // (3) numZoomLevels set on map
41877         // (4) maxZoomLevel set on map*
41878         // (5) none of the above*
41879         //
41880         // *Note that options (4) and (5) are only possible if the user 
41881         //  _explicitly_ sets the 'numZoomLevels' property on the map to 
41882         //  null, since it is set by default to 16. 
41883         //
41884
41885         //
41886         // Note to future: In 3.0, I think we should remove the default 
41887         // value of 16 for map.numZoomLevels. Rather, I think that value 
41888         // should be set as a default on the Layer.WMS class. If someone
41889         // creates a 3rd party layer and does not specify any 'minZoomLevel', 
41890         // 'maxZoomLevel', or 'numZoomLevels', and has not explicitly 
41891         // specified any of those on the map object either.. then I think
41892         // it is fair to say that s/he wants all the zoom levels available.
41893         // 
41894         // By making map.numZoomLevels *null* by default, that will be the 
41895         // case. As it is, I don't feel comfortable changing that right now
41896         // as it would be a glaring API change and actually would probably
41897         // break many peoples' codes. 
41898         //
41899
41900         //the number of zoom levels we'd like to have.
41901         var desiredZoomLevels;
41902
41903         //this is the maximum number of zoom levels the layer will allow, 
41904         // given the specified starting minimum zoom level.
41905         var limitZoomLevels = this.MAX_ZOOM_LEVEL - this.minZoomLevel + 1;
41906
41907         if ( ((this.options.numZoomLevels == null) && 
41908               (this.options.maxZoomLevel != null)) // (2)
41909               ||
41910              ((this.numZoomLevels == null) &&
41911               (this.maxZoomLevel != null)) // (4)
41912            ) {
41913             //calculate based on specified maxZoomLevel (on layer or map)
41914             desiredZoomLevels = this.maxZoomLevel - this.minZoomLevel + 1;
41915         } else {
41916             //calculate based on specified numZoomLevels (on layer or map)
41917             // this covers cases (1) and (3)
41918             desiredZoomLevels = this.numZoomLevels;
41919         }
41920
41921         if (desiredZoomLevels != null) {
41922             //Now that we know what we would *like* the number of zoom levels
41923             // to be, based on layer or map options, we have to make sure that
41924             // it does not conflict with the actual limit, as specified by 
41925             // the constants on the layer itself (and calculated into the
41926             // 'limitZoomLevels' variable). 
41927             this.numZoomLevels = Math.min(desiredZoomLevels, limitZoomLevels);
41928         } else {
41929             // case (5) -- neither 'numZoomLevels' not 'maxZoomLevel' was 
41930             // set on either the layer or the map. So we just use the 
41931             // maximum limit as calculated by the layer's constants.
41932             this.numZoomLevels = limitZoomLevels;
41933         }
41934
41935         //now that the 'numZoomLevels' is appropriately, safely set, 
41936         // we go back and re-calculate the 'maxZoomLevel'.
41937         this.maxZoomLevel = this.minZoomLevel + this.numZoomLevels - 1;
41938
41939         if (this.RESOLUTIONS != null) {
41940             var resolutionsIndex = 0;
41941             this.resolutions = [];
41942             for(var i= this.minZoomLevel; i <= this.maxZoomLevel; i++) {
41943                 this.resolutions[resolutionsIndex++] = this.RESOLUTIONS[i];            
41944             }
41945             this.maxResolution = this.resolutions[0];
41946             this.minResolution = this.resolutions[this.resolutions.length - 1];
41947         }       
41948     },
41949     
41950     /**
41951      * APIMethod: getResolution
41952      * Get the current map resolution
41953      * 
41954      * Returns:
41955      * {Float} Map units per Pixel
41956      */
41957     getResolution: function() {
41958
41959         if (this.resolutions != null) {
41960             return OpenLayers.Layer.prototype.getResolution.apply(this, arguments);
41961         } else {
41962             var resolution = null;
41963             
41964             var viewSize = this.map.getSize();
41965             var extent = this.getExtent();
41966             
41967             if ((viewSize != null) && (extent != null)) {
41968                 resolution = Math.max( extent.getWidth()  / viewSize.w,
41969                                        extent.getHeight() / viewSize.h );
41970             }
41971             return resolution;
41972         }
41973      },
41974
41975     /**
41976      * APIMethod: getExtent
41977      * Calculates using px-> lonlat translation functions on tl and br 
41978      *     corners of viewport
41979      * 
41980      * Returns:
41981      * {<OpenLayers.Bounds>} A Bounds object which represents the lon/lat 
41982      *                       bounds of the current viewPort.
41983      */
41984     getExtent: function () {
41985         var size = this.map.getSize();
41986         var tl = this.getLonLatFromViewPortPx({
41987             x: 0, y: 0
41988         });
41989         var br = this.getLonLatFromViewPortPx({
41990             x: size.w, y: size.h
41991         });
41992         
41993         if ((tl != null) && (br != null)) {
41994             return new OpenLayers.Bounds(tl.lon, br.lat, br.lon, tl.lat);
41995         } else {
41996             return null;
41997         }
41998     },
41999
42000     /**
42001      * Method: getZoomForResolution
42002      * Get the zoom level for a given resolution
42003      *
42004      * Parameters:
42005      * resolution - {Float}
42006      *
42007      * Returns:
42008      * {Integer} A suitable zoom level for the specified resolution.
42009      *           If no baselayer is set, returns null.
42010      */
42011     getZoomForResolution: function(resolution) {
42012       
42013         if (this.resolutions != null) {
42014             return OpenLayers.Layer.prototype.getZoomForResolution.apply(this, arguments);
42015         } else {
42016             var extent = OpenLayers.Layer.prototype.getExtent.apply(this, []);
42017             return this.getZoomForExtent(extent);
42018         }
42019     },
42020
42021
42022
42023     
42024     /********************************************************/
42025     /*                                                      */
42026     /*             Translation Functions                    */
42027     /*                                                      */
42028     /*    The following functions translate GMaps and OL    */ 
42029     /*     formats for Pixel, LonLat, Bounds, and Zoom      */
42030     /*                                                      */
42031     /********************************************************/
42032     
42033     
42034     //
42035     // TRANSLATION: MapObject Zoom <-> OpenLayers Zoom
42036     //
42037   
42038     /**
42039      * Method: getOLZoomFromMapObjectZoom
42040      * Get the OL zoom index from the map object zoom level
42041      *
42042      * Parameters:
42043      * moZoom - {Integer}
42044      * 
42045      * Returns:
42046      * {Integer} An OpenLayers Zoom level, translated from the passed in zoom
42047      *           Returns null if null value is passed in
42048      */
42049     getOLZoomFromMapObjectZoom: function(moZoom) {
42050         var zoom = null;
42051         if (moZoom != null) {
42052             zoom = moZoom - this.minZoomLevel;
42053             if (this.map.baseLayer !== this) {
42054                 zoom = this.map.baseLayer.getZoomForResolution(
42055                     this.getResolutionForZoom(zoom)
42056                 );
42057             }
42058         }
42059         return zoom;
42060     },
42061     
42062     /**
42063      * Method: getMapObjectZoomFromOLZoom
42064      * Get the map object zoom level from the OL zoom level
42065      *
42066      * Parameters:
42067      * olZoom - {Integer}
42068      * 
42069      * Returns:
42070      * {Integer} A MapObject level, translated from the passed in olZoom
42071      *           Returns null if null value is passed in
42072      */
42073     getMapObjectZoomFromOLZoom: function(olZoom) {
42074         var zoom = null; 
42075         if (olZoom != null) {
42076             zoom = olZoom + this.minZoomLevel;
42077             if (this.map.baseLayer !== this) {
42078                 zoom = this.getZoomForResolution(
42079                     this.map.baseLayer.getResolutionForZoom(zoom)
42080                 );
42081             }
42082         }
42083         return zoom;
42084     },
42085
42086     CLASS_NAME: "OpenLayers.Layer.FixedZoomLevels"
42087 });
42088
42089 /* ======================================================================
42090     OpenLayers/Layer/Google.js
42091    ====================================================================== */
42092
42093 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
42094  * full list of contributors). Published under the 2-clause BSD license.
42095  * See license.txt in the OpenLayers distribution or repository for the
42096  * full text of the license. */
42097
42098
42099 /**
42100  * @requires OpenLayers/Layer/SphericalMercator.js
42101  * @requires OpenLayers/Layer/EventPane.js
42102  * @requires OpenLayers/Layer/FixedZoomLevels.js
42103  * @requires OpenLayers/Lang.js
42104  */
42105
42106 /**
42107  * Class: OpenLayers.Layer.Google
42108  *
42109  * Provides a wrapper for Google's Maps API
42110  * Normally the Terms of Use for this API do not allow wrapping, but Google
42111  * have provided written consent to OpenLayers for this - see email in
42112  * http://osgeo-org.1560.n6.nabble.com/Google-Maps-API-Terms-of-Use-changes-tp4910013p4911981.html
42113  *
42114  * Inherits from:
42115  *  - <OpenLayers.Layer.SphericalMercator>
42116  *  - <OpenLayers.Layer.EventPane>
42117  *  - <OpenLayers.Layer.FixedZoomLevels>
42118  */
42119 OpenLayers.Layer.Google = OpenLayers.Class(
42120     OpenLayers.Layer.EventPane,
42121     OpenLayers.Layer.FixedZoomLevels, {
42122
42123     /**
42124      * Constant: MIN_ZOOM_LEVEL
42125      * {Integer} 0
42126      */
42127     MIN_ZOOM_LEVEL: 0,
42128
42129     /**
42130      * Constant: MAX_ZOOM_LEVEL
42131      * {Integer} 21
42132      */
42133     MAX_ZOOM_LEVEL: 21,
42134
42135     /**
42136      * Constant: RESOLUTIONS
42137      * {Array(Float)} Hardcode these resolutions so that they are more closely
42138      *                tied with the standard wms projection
42139      */
42140     RESOLUTIONS: [
42141         1.40625,
42142         0.703125,
42143         0.3515625,
42144         0.17578125,
42145         0.087890625,
42146         0.0439453125,
42147         0.02197265625,
42148         0.010986328125,
42149         0.0054931640625,
42150         0.00274658203125,
42151         0.001373291015625,
42152         0.0006866455078125,
42153         0.00034332275390625,
42154         0.000171661376953125,
42155         0.0000858306884765625,
42156         0.00004291534423828125,
42157         0.00002145767211914062,
42158         0.00001072883605957031,
42159         0.00000536441802978515,
42160         0.00000268220901489257,
42161         0.0000013411045074462891,
42162         0.00000067055225372314453
42163     ],
42164
42165     /**
42166      * APIProperty: type
42167      * {GMapType}
42168      */
42169     type: null,
42170
42171     /**
42172      * APIProperty: wrapDateLine
42173      * {Boolean} Allow user to pan forever east/west.  Default is true.
42174      *     Setting this to false only restricts panning if
42175      *     <sphericalMercator> is true.
42176      */
42177     wrapDateLine: true,
42178
42179     /**
42180      * APIProperty: sphericalMercator
42181      * {Boolean} Should the map act as a mercator-projected map? This will
42182      *     cause all interactions with the map to be in the actual map
42183      *     projection, which allows support for vector drawing, overlaying
42184      *     other maps, etc.
42185      */
42186     sphericalMercator: false,
42187
42188     /**
42189      * APIProperty: useTiltImages
42190      * {Boolean} Should Google use 45° (tilt) imagery when available or
42191      *     should it stick to the 0° overhead view? While tilt images look
42192      *     impressive, the changed viewing angle can cause the misalignment
42193      *     of overlay layers.
42194      */
42195     useTiltImages: false,
42196
42197     /**
42198      * Property: version
42199      * {Number} The version of the Google Maps API
42200      */
42201     version: null,
42202
42203     /**
42204      * Constructor: OpenLayers.Layer.Google
42205      *
42206      * Parameters:
42207      * name - {String} A name for the layer.
42208      * options - {Object} An optional object whose properties will be set
42209      *     on the layer.
42210      */
42211     initialize: function(name, options) {
42212         options = options || {};
42213         options.version = "3";
42214         var mixin = OpenLayers.Layer.Google["v" +
42215             options.version.replace(/\./g, "_")];
42216         if (mixin) {
42217             OpenLayers.Util.applyDefaults(options, mixin);
42218         } else {
42219             throw "Unsupported Google Maps API version: " + options.version;
42220         }
42221
42222         OpenLayers.Util.applyDefaults(options, mixin.DEFAULTS);
42223         if (options.maxExtent) {
42224             options.maxExtent = options.maxExtent.clone();
42225         }
42226
42227         OpenLayers.Layer.EventPane.prototype.initialize.apply(this,
42228             [name, options]);
42229         OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this,
42230             [name, options]);
42231
42232         if (this.sphericalMercator) {
42233             OpenLayers.Util.extend(this, OpenLayers.Layer.SphericalMercator);
42234             this.initMercatorParameters();
42235         }
42236     },
42237
42238     /**
42239      * Method: clone
42240      * Create a clone of this layer
42241      *
42242      * Returns:
42243      * {<OpenLayers.Layer.Google>} An exact clone of this layer
42244      */
42245     clone: function() {
42246         /**
42247          * This method isn't intended to be called by a subclass and it
42248          * doesn't call the same method on the superclass.  We don't call
42249          * the super's clone because we don't want properties that are set
42250          * on this layer after initialize (i.e. this.mapObject etc.).
42251          */
42252         return new OpenLayers.Layer.Google(
42253             this.name, this.getOptions()
42254         );
42255     },
42256
42257     /**
42258      * APIMethod: setVisibility
42259      * Set the visibility flag for the layer and hide/show & redraw
42260      *     accordingly. Fire event unless otherwise specified
42261      *
42262      * Note that visibility is no longer simply whether or not the layer's
42263      *     style.display is set to "block". Now we store a 'visibility' state
42264      *     property on the layer class, this allows us to remember whether or
42265      *     not we *desire* for a layer to be visible. In the case where the
42266      *     map's resolution is out of the layer's range, this desire may be
42267      *     subverted.
42268      *
42269      * Parameters:
42270      * visible - {Boolean} Display the layer (if in range)
42271      */
42272     setVisibility: function(visible) {
42273         // sharing a map container, opacity has to be set per layer
42274         var opacity = this.opacity == null ? 1 : this.opacity;
42275         OpenLayers.Layer.EventPane.prototype.setVisibility.apply(this, arguments);
42276         this.setOpacity(opacity);
42277     },
42278
42279     /**
42280      * APIMethod: display
42281      * Hide or show the Layer
42282      *
42283      * Parameters:
42284      * visible - {Boolean}
42285      */
42286     display: function(visible) {
42287         if (!this._dragging) {
42288             this.setGMapVisibility(visible);
42289         }
42290         OpenLayers.Layer.EventPane.prototype.display.apply(this, arguments);
42291     },
42292
42293     /**
42294      * Method: moveTo
42295      *
42296      * Parameters:
42297      * bounds - {<OpenLayers.Bounds>}
42298      * zoomChanged - {Boolean} Tells when zoom has changed, as layers have to
42299      *     do some init work in that case.
42300      * dragging - {Boolean}
42301      */
42302     moveTo: function(bounds, zoomChanged, dragging) {
42303         this._dragging = dragging;
42304         OpenLayers.Layer.EventPane.prototype.moveTo.apply(this, arguments);
42305         delete this._dragging;
42306     },
42307
42308     /**
42309      * APIMethod: setOpacity
42310      * Sets the opacity for the entire layer (all images)
42311      *
42312      * Parameters:
42313      * opacity - {Float}
42314      */
42315     setOpacity: function(opacity) {
42316         if (opacity !== this.opacity) {
42317             if (this.map != null) {
42318                 this.map.events.triggerEvent("changelayer", {
42319                     layer: this,
42320                     property: "opacity"
42321                 });
42322             }
42323             this.opacity = opacity;
42324         }
42325         // Though this layer's opacity may not change, we're sharing a container
42326         // and need to update the opacity for the entire container.
42327         if (this.getVisibility()) {
42328             var container = this.getMapContainer();
42329             OpenLayers.Util.modifyDOMElement(
42330                 container, null, null, null, null, null, null, opacity
42331             );
42332         }
42333     },
42334
42335     /**
42336      * APIMethod: destroy
42337      * Clean up this layer.
42338      */
42339     destroy: function() {
42340         /**
42341          * We have to override this method because the event pane destroy
42342          * deletes the mapObject reference before removing this layer from
42343          * the map.
42344          */
42345         if (this.map) {
42346             this.setGMapVisibility(false);
42347             var cache = OpenLayers.Layer.Google.cache[this.map.id];
42348             if (cache && cache.count <= 1) {
42349                 this.removeGMapElements();
42350             }
42351         }
42352         OpenLayers.Layer.EventPane.prototype.destroy.apply(this, arguments);
42353     },
42354
42355     /**
42356      * Method: removeGMapElements
42357      * Remove all elements added to the dom.  This should only be called if
42358      * this is the last of the Google layers for the given map.
42359      */
42360     removeGMapElements: function() {
42361         var cache = OpenLayers.Layer.Google.cache[this.map.id];
42362         if (cache) {
42363             // remove shared elements from dom
42364             var container = this.mapObject && this.getMapContainer();
42365             if (container && container.parentNode) {
42366                 container.parentNode.removeChild(container);
42367             }
42368             var termsOfUse = cache.termsOfUse;
42369             if (termsOfUse && termsOfUse.parentNode) {
42370                 termsOfUse.parentNode.removeChild(termsOfUse);
42371             }
42372             var poweredBy = cache.poweredBy;
42373             if (poweredBy && poweredBy.parentNode) {
42374                 poweredBy.parentNode.removeChild(poweredBy);
42375             }
42376             if (this.mapObject && window.google && google.maps &&
42377                     google.maps.event && google.maps.event.clearListeners) {
42378                 google.maps.event.clearListeners(this.mapObject, 'tilesloaded');
42379             }
42380         }
42381     },
42382
42383     /**
42384      * APIMethod: removeMap
42385      * On being removed from the map, also remove termsOfUse and poweredBy divs
42386      *
42387      * Parameters:
42388      * map - {<OpenLayers.Map>}
42389      */
42390     removeMap: function(map) {
42391         // hide layer before removing
42392         if (this.visibility && this.mapObject) {
42393             this.setGMapVisibility(false);
42394         }
42395         // check to see if last Google layer in this map
42396         var cache = OpenLayers.Layer.Google.cache[map.id];
42397         if (cache) {
42398             if (cache.count <= 1) {
42399                 this.removeGMapElements();
42400                 delete OpenLayers.Layer.Google.cache[map.id];
42401             } else {
42402                 // decrement the layer count
42403                 --cache.count;
42404             }
42405         }
42406         // remove references to gmap elements
42407         delete this.termsOfUse;
42408         delete this.poweredBy;
42409         delete this.mapObject;
42410         delete this.dragObject;
42411         OpenLayers.Layer.EventPane.prototype.removeMap.apply(this, arguments);
42412     },
42413
42414   //
42415   // TRANSLATION: MapObject Bounds <-> OpenLayers.Bounds
42416   //
42417
42418     /**
42419      * APIMethod: getOLBoundsFromMapObjectBounds
42420      *
42421      * Parameters:
42422      * moBounds - {Object}
42423      *
42424      * Returns:
42425      * {<OpenLayers.Bounds>} An <OpenLayers.Bounds>, translated from the
42426      *                       passed-in MapObject Bounds.
42427      *                       Returns null if null value is passed in.
42428      */
42429     getOLBoundsFromMapObjectBounds: function(moBounds) {
42430         var olBounds = null;
42431         if (moBounds != null) {
42432             var sw = moBounds.getSouthWest();
42433             var ne = moBounds.getNorthEast();
42434             if (this.sphericalMercator) {
42435                 sw = this.forwardMercator(sw.lng(), sw.lat());
42436                 ne = this.forwardMercator(ne.lng(), ne.lat());
42437             } else {
42438                 sw = new OpenLayers.LonLat(sw.lng(), sw.lat());
42439                 ne = new OpenLayers.LonLat(ne.lng(), ne.lat());
42440             }
42441             olBounds = new OpenLayers.Bounds(sw.lon,
42442                                              sw.lat,
42443                                              ne.lon,
42444                                              ne.lat );
42445         }
42446         return olBounds;
42447     },
42448
42449     /**
42450      * APIMethod: getWarningHTML
42451      *
42452      * Returns:
42453      * {String} String with information on why layer is broken, how to get
42454      *          it working.
42455      */
42456     getWarningHTML:function() {
42457         return OpenLayers.i18n("googleWarning");
42458     },
42459
42460
42461     /************************************
42462      *                                  *
42463      *   MapObject Interface Controls   *
42464      *                                  *
42465      ************************************/
42466
42467
42468   // Get&Set Center, Zoom
42469
42470     /**
42471      * APIMethod: getMapObjectCenter
42472      *
42473      * Returns:
42474      * {Object} The mapObject's current center in Map Object format
42475      */
42476     getMapObjectCenter: function() {
42477         return this.mapObject.getCenter();
42478     },
42479
42480     /**
42481      * APIMethod: getMapObjectZoom
42482      *
42483      * Returns:
42484      * {Integer} The mapObject's current zoom, in Map Object format
42485      */
42486     getMapObjectZoom: function() {
42487         return this.mapObject.getZoom();
42488     },
42489
42490
42491     /************************************
42492      *                                  *
42493      *       MapObject Primitives       *
42494      *                                  *
42495      ************************************/
42496
42497
42498   // LonLat
42499
42500     /**
42501      * APIMethod: getLongitudeFromMapObjectLonLat
42502      *
42503      * Parameters:
42504      * moLonLat - {Object} MapObject LonLat format
42505      *
42506      * Returns:
42507      * {Float} Longitude of the given MapObject LonLat
42508      */
42509     getLongitudeFromMapObjectLonLat: function(moLonLat) {
42510         return this.sphericalMercator ?
42511           this.forwardMercator(moLonLat.lng(), moLonLat.lat()).lon :
42512           moLonLat.lng();
42513     },
42514
42515     /**
42516      * APIMethod: getLatitudeFromMapObjectLonLat
42517      *
42518      * Parameters:
42519      * moLonLat - {Object} MapObject LonLat format
42520      *
42521      * Returns:
42522      * {Float} Latitude of the given MapObject LonLat
42523      */
42524     getLatitudeFromMapObjectLonLat: function(moLonLat) {
42525         var lat = this.sphericalMercator ?
42526           this.forwardMercator(moLonLat.lng(), moLonLat.lat()).lat :
42527           moLonLat.lat();
42528         return lat;
42529     },
42530
42531   // Pixel
42532
42533     /**
42534      * APIMethod: getXFromMapObjectPixel
42535      *
42536      * Parameters:
42537      * moPixel - {Object} MapObject Pixel format
42538      *
42539      * Returns:
42540      * {Integer} X value of the MapObject Pixel
42541      */
42542     getXFromMapObjectPixel: function(moPixel) {
42543         return moPixel.x;
42544     },
42545
42546     /**
42547      * APIMethod: getYFromMapObjectPixel
42548      *
42549      * Parameters:
42550      * moPixel - {Object} MapObject Pixel format
42551      *
42552      * Returns:
42553      * {Integer} Y value of the MapObject Pixel
42554      */
42555     getYFromMapObjectPixel: function(moPixel) {
42556         return moPixel.y;
42557     },
42558
42559     CLASS_NAME: "OpenLayers.Layer.Google"
42560 });
42561
42562 /**
42563  * Property: OpenLayers.Layer.Google.cache
42564  * {Object} Cache for elements that should only be created once per map.
42565  */
42566 OpenLayers.Layer.Google.cache = {};
42567 /* ======================================================================
42568     OpenLayers/Layer/Google/v3.js
42569    ====================================================================== */
42570
42571 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
42572  * full list of contributors). Published under the 2-clause BSD license.
42573  * See license.txt in the OpenLayers distribution or repository for the
42574  * full text of the license. */
42575
42576
42577 /**
42578  * @requires OpenLayers/Layer/Google.js
42579  */
42580
42581 /**
42582  * Constant: OpenLayers.Layer.Google.v3
42583  *
42584  * Mixin providing functionality specific to the Google Maps API v3.
42585  *
42586  * To use this layer, you must include the GMaps v3 API in your html. To match
42587  * Google's zoom animation better with OpenLayers animated zooming, configure
42588  * your map with a zoomDuration of 10:
42589  *
42590  * (code)
42591  * new OpenLayers.Map('map', {zoomDuration: 10});
42592  * (end)
42593  *
42594  * Note that this layer configures the google.maps.map object with the
42595  * "disableDefaultUI" option set to true. Using UI controls that the Google
42596  * Maps API provides is not supported by the OpenLayers API.
42597  */
42598 OpenLayers.Layer.Google.v3 = {
42599
42600     /**
42601      * Constant: DEFAULTS
42602      * {Object} It is not recommended to change the properties set here. Note
42603      * that Google.v3 layers only work when sphericalMercator is set to true.
42604      *
42605      * (code)
42606      * {
42607      *     sphericalMercator: true,
42608      *     projection: "EPSG:900913"
42609      * }
42610      * (end)
42611      */
42612     DEFAULTS: {
42613         sphericalMercator: true,
42614         projection: "EPSG:900913"
42615     },
42616
42617     /**
42618      * APIProperty: animationEnabled
42619      * {Boolean} If set to true, the transition between zoom levels will be
42620      *     animated (if supported by the GMaps API for the device used). Set to
42621      *     false to match the zooming experience of other layer types. Default
42622      *     is true. Note that the GMaps API does not give us control over zoom
42623      *     animation, so if set to false, when zooming, this will make the
42624      *     layer temporarily invisible, wait until GMaps reports the map being
42625      *     idle, and make it visible again. The result will be a blank layer
42626      *     for a few moments while zooming.
42627      */
42628     animationEnabled: true,
42629
42630     /**
42631      * Method: loadMapObject
42632      * Load the GMap and register appropriate event listeners.
42633      */
42634     loadMapObject: function() {
42635         if (!this.type) {
42636             this.type = google.maps.MapTypeId.ROADMAP;
42637         }
42638         var mapObject;
42639         var cache = OpenLayers.Layer.Google.cache[this.map.id];
42640         if (cache) {
42641             // there are already Google layers added to this map
42642             mapObject = cache.mapObject;
42643             // increment the layer count
42644             ++cache.count;
42645         } else {
42646             // this is the first Google layer for this map
42647             // create GMap
42648             var center = this.map.getCenter();
42649             var container = document.createElement('div');
42650             container.className = "olForeignContainer";
42651             container.style.width = '100%';
42652             container.style.height = '100%';
42653             mapObject = new google.maps.Map(container, {
42654                 center: center ?
42655                     new google.maps.LatLng(center.lat, center.lon) :
42656                     new google.maps.LatLng(0, 0),
42657                 zoom: this.map.getZoom() || 0,
42658                 mapTypeId: this.type,
42659                 disableDefaultUI: true,
42660                 keyboardShortcuts: false,
42661                 draggable: false,
42662                 disableDoubleClickZoom: true,
42663                 scrollwheel: false,
42664                 streetViewControl: false,
42665                 tilt: (this.useTiltImages ? 45: 0)
42666             });
42667             var googleControl = document.createElement('div');
42668             googleControl.style.width = '100%';
42669             googleControl.style.height = '100%';
42670             mapObject.controls[google.maps.ControlPosition.TOP_LEFT].push(googleControl);
42671
42672             // cache elements for use by any other google layers added to
42673             // this same map
42674             cache = {
42675                 googleControl: googleControl,
42676                 mapObject: mapObject,
42677                 count: 1
42678             };
42679             OpenLayers.Layer.Google.cache[this.map.id] = cache;
42680         }
42681         this.mapObject = mapObject;
42682         this.setGMapVisibility(this.visibility);
42683     },
42684
42685     /**
42686      * APIMethod: onMapResize
42687      */
42688     onMapResize: function() {
42689         if (this.visibility) {
42690             google.maps.event.trigger(this.mapObject, "resize");
42691         }
42692     },
42693
42694     /**
42695      * Method: setGMapVisibility
42696      * Display the GMap container and associated elements.
42697      *
42698      * Parameters:
42699      * visible - {Boolean} Display the GMap elements.
42700      */
42701     setGMapVisibility: function(visible) {
42702         var cache = OpenLayers.Layer.Google.cache[this.map.id];
42703         var map = this.map;
42704         if (cache) {
42705             var type = this.type;
42706             var layers = map.layers;
42707             var layer;
42708             for (var i=layers.length-1; i>=0; --i) {
42709                 layer = layers[i];
42710                 if (layer instanceof OpenLayers.Layer.Google &&
42711                             layer.visibility === true && layer.inRange === true) {
42712                     type = layer.type;
42713                     visible = true;
42714                     break;
42715                 }
42716             }
42717             var container = this.mapObject.getDiv();
42718             if (visible === true) {
42719                 if (container.parentNode !== map.div) {
42720                     if (!cache.rendered) {
42721                         container.style.visibility = 'hidden';
42722                         var me = this;
42723                         google.maps.event.addListenerOnce(this.mapObject, 'tilesloaded', function() {
42724                             cache.rendered = true;
42725                             container.style.visibility = '';
42726                             me.setGMapVisibility(true);
42727                             me.moveTo(me.map.getCenter());
42728                             cache.googleControl.appendChild(map.viewPortDiv);
42729                             me.setGMapVisibility(me.visible);
42730                         });
42731                     } else {
42732                         cache.googleControl.appendChild(map.viewPortDiv);
42733                     }
42734                     map.div.appendChild(container);
42735                     google.maps.event.trigger(this.mapObject, 'resize');
42736                 }
42737                 this.mapObject.setMapTypeId(type);
42738             } else if (cache.googleControl.hasChildNodes()) {
42739                 map.div.appendChild(map.viewPortDiv);
42740                 if (map.div.contains(container)) {
42741                     map.div.removeChild(container);
42742                 }
42743             }
42744         }
42745     },
42746
42747     /**
42748      * Method: getMapContainer
42749      *
42750      * Returns:
42751      * {DOMElement} the GMap container's div
42752      */
42753     getMapContainer: function() {
42754         return this.mapObject.getDiv();
42755     },
42756
42757   //
42758   // TRANSLATION: MapObject Bounds <-> OpenLayers.Bounds
42759   //
42760
42761     /**
42762      * APIMethod: getMapObjectBoundsFromOLBounds
42763      *
42764      * Parameters:
42765      * olBounds - {<OpenLayers.Bounds>}
42766      *
42767      * Returns:
42768      * {Object} A MapObject Bounds, translated from olBounds
42769      *          Returns null if null value is passed in
42770      */
42771     getMapObjectBoundsFromOLBounds: function(olBounds) {
42772         var moBounds = null;
42773         if (olBounds != null) {
42774             var sw = this.sphericalMercator ?
42775               this.inverseMercator(olBounds.bottom, olBounds.left) :
42776               new OpenLayers.LonLat(olBounds.bottom, olBounds.left);
42777             var ne = this.sphericalMercator ?
42778               this.inverseMercator(olBounds.top, olBounds.right) :
42779               new OpenLayers.LonLat(olBounds.top, olBounds.right);
42780             moBounds = new google.maps.LatLngBounds(
42781                 new google.maps.LatLng(sw.lat, sw.lon),
42782                 new google.maps.LatLng(ne.lat, ne.lon)
42783             );
42784         }
42785         return moBounds;
42786     },
42787
42788
42789     /************************************
42790      *                                  *
42791      *   MapObject Interface Controls   *
42792      *                                  *
42793      ************************************/
42794
42795
42796   // LonLat - Pixel Translation
42797
42798     /**
42799      * APIMethod: getMapObjectLonLatFromMapObjectPixel
42800      *
42801      * Parameters:
42802      * moPixel - {Object} MapObject Pixel format
42803      *
42804      * Returns:
42805      * {Object} MapObject LonLat translated from MapObject Pixel
42806      */
42807     getMapObjectLonLatFromMapObjectPixel: function(moPixel) {
42808         var size = this.map.getSize();
42809         var lon = this.getLongitudeFromMapObjectLonLat(this.mapObject.center);
42810         var lat = this.getLatitudeFromMapObjectLonLat(this.mapObject.center);
42811         var res = this.map.getResolution();
42812
42813         var delta_x = moPixel.x - (size.w / 2);
42814         var delta_y = moPixel.y - (size.h / 2);
42815
42816         var lonlat = new OpenLayers.LonLat(
42817             lon + delta_x * res,
42818             lat - delta_y * res
42819         );
42820
42821         if (this.wrapDateLine) {
42822             lonlat = lonlat.wrapDateLine(this.maxExtent);
42823         }
42824         return this.getMapObjectLonLatFromLonLat(lonlat.lon, lonlat.lat);
42825     },
42826
42827     /**
42828      * APIMethod: getMapObjectPixelFromMapObjectLonLat
42829      *
42830      * Parameters:
42831      * moLonLat - {Object} MapObject LonLat format
42832      *
42833      * Returns:
42834      * {Object} MapObject Pixel transtlated from MapObject LonLat
42835      */
42836     getMapObjectPixelFromMapObjectLonLat: function(moLonLat) {
42837         var lon = this.getLongitudeFromMapObjectLonLat(moLonLat);
42838         var lat = this.getLatitudeFromMapObjectLonLat(moLonLat);
42839         var res = this.map.getResolution();
42840         var extent = this.map.getExtent();
42841         return this.getMapObjectPixelFromXY((1/res * (lon - extent.left)),
42842                                             (1/res * (extent.top - lat)));
42843     },
42844
42845
42846     /**
42847      * APIMethod: setMapObjectCenter
42848      * Set the mapObject to the specified center and zoom
42849      *
42850      * Parameters:
42851      * center - {Object} MapObject LonLat format
42852      * zoom - {int} MapObject zoom format
42853      */
42854     setMapObjectCenter: function(center, zoom) {
42855         if (this.animationEnabled === false && zoom != this.mapObject.zoom) {
42856             var mapContainer = this.getMapContainer();
42857             google.maps.event.addListenerOnce(
42858                 this.mapObject,
42859                 "idle",
42860                 function() {
42861                     mapContainer.style.visibility = "";
42862                 }
42863             );
42864             mapContainer.style.visibility = "hidden";
42865         }
42866         this.mapObject.setOptions({
42867             center: center,
42868             zoom: zoom
42869         });
42870     },
42871
42872
42873   // Bounds
42874
42875     /**
42876      * APIMethod: getMapObjectZoomFromMapObjectBounds
42877      *
42878      * Parameters:
42879      * moBounds - {Object} MapObject Bounds format
42880      *
42881      * Returns:
42882      * {Object} MapObject Zoom for specified MapObject Bounds
42883      */
42884     getMapObjectZoomFromMapObjectBounds: function(moBounds) {
42885         return this.mapObject.getBoundsZoomLevel(moBounds);
42886     },
42887
42888     /************************************
42889      *                                  *
42890      *       MapObject Primitives       *
42891      *                                  *
42892      ************************************/
42893
42894
42895   // LonLat
42896
42897     /**
42898      * APIMethod: getMapObjectLonLatFromLonLat
42899      *
42900      * Parameters:
42901      * lon - {Float}
42902      * lat - {Float}
42903      *
42904      * Returns:
42905      * {Object} MapObject LonLat built from lon and lat params
42906      */
42907     getMapObjectLonLatFromLonLat: function(lon, lat) {
42908         var gLatLng;
42909         if(this.sphericalMercator) {
42910             var lonlat = this.inverseMercator(lon, lat);
42911             gLatLng = new google.maps.LatLng(lonlat.lat, lonlat.lon);
42912         } else {
42913             gLatLng = new google.maps.LatLng(lat, lon);
42914         }
42915         return gLatLng;
42916     },
42917
42918   // Pixel
42919
42920     /**
42921      * APIMethod: getMapObjectPixelFromXY
42922      *
42923      * Parameters:
42924      * x - {Integer}
42925      * y - {Integer}
42926      *
42927      * Returns:
42928      * {Object} MapObject Pixel from x and y parameters
42929      */
42930     getMapObjectPixelFromXY: function(x, y) {
42931         return new google.maps.Point(x, y);
42932     }
42933
42934 };
42935 /* ======================================================================
42936     OpenLayers/Strategy.js
42937    ====================================================================== */
42938
42939 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
42940  * full list of contributors). Published under the 2-clause BSD license.
42941  * See license.txt in the OpenLayers distribution or repository for the
42942  * full text of the license. */
42943
42944 /**
42945  * @requires OpenLayers/BaseTypes/Class.js
42946  */
42947
42948 /**
42949  * Class: OpenLayers.Strategy
42950  * Abstract vector layer strategy class.  Not to be instantiated directly.  Use
42951  *     one of the strategy subclasses instead.
42952  */
42953 OpenLayers.Strategy = OpenLayers.Class({
42954     
42955     /**
42956      * Property: layer
42957      * {<OpenLayers.Layer.Vector>} The layer this strategy belongs to.
42958      */
42959     layer: null,
42960     
42961     /**
42962      * Property: options
42963      * {Object} Any options sent to the constructor.
42964      */
42965     options: null,
42966
42967     /** 
42968      * Property: active 
42969      * {Boolean} The control is active.
42970      */
42971     active: null,
42972
42973     /**
42974      * Property: autoActivate
42975      * {Boolean} The creator of the strategy can set autoActivate to false
42976      *      to fully control when the protocol is activated and deactivated.
42977      *      Defaults to true.
42978      */
42979     autoActivate: true,
42980
42981     /**
42982      * Property: autoDestroy
42983      * {Boolean} The creator of the strategy can set autoDestroy to false
42984      *      to fully control when the strategy is destroyed. Defaults to
42985      *      true.
42986      */
42987     autoDestroy: true,
42988
42989     /**
42990      * Constructor: OpenLayers.Strategy
42991      * Abstract class for vector strategies.  Create instances of a subclass.
42992      *
42993      * Parameters:
42994      * options - {Object} Optional object whose properties will be set on the
42995      *     instance.
42996      */
42997     initialize: function(options) {
42998         OpenLayers.Util.extend(this, options);
42999         this.options = options;
43000         // set the active property here, so that user cannot override it
43001         this.active = false;
43002     },
43003     
43004     /**
43005      * APIMethod: destroy
43006      * Clean up the strategy.
43007      */
43008     destroy: function() {
43009         this.deactivate();
43010         this.layer = null;
43011         this.options = null;
43012     },
43013
43014     /**
43015      * Method: setLayer
43016      * Called to set the <layer> property.
43017      *
43018      * Parameters:
43019      * layer - {<OpenLayers.Layer.Vector>}
43020      */
43021     setLayer: function(layer) {
43022         this.layer = layer;
43023     },
43024     
43025     /**
43026      * Method: activate
43027      * Activate the strategy.  Register any listeners, do appropriate setup.
43028      *
43029      * Returns:
43030      * {Boolean} True if the strategy was successfully activated or false if
43031      *      the strategy was already active.
43032      */
43033     activate: function() {
43034         if (!this.active) {
43035             this.active = true;
43036             return true;
43037         }
43038         return false;
43039     },
43040     
43041     /**
43042      * Method: deactivate
43043      * Deactivate the strategy.  Unregister any listeners, do appropriate
43044      *     tear-down.
43045      *
43046      * Returns:
43047      * {Boolean} True if the strategy was successfully deactivated or false if
43048      *      the strategy was already inactive.
43049      */
43050     deactivate: function() {
43051         if (this.active) {
43052             this.active = false;
43053             return true;
43054         }
43055         return false;
43056     },
43057    
43058     CLASS_NAME: "OpenLayers.Strategy" 
43059 });
43060 /* ======================================================================
43061     OpenLayers/Strategy/Filter.js
43062    ====================================================================== */
43063
43064 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
43065  * full list of contributors). Published under the 2-clause BSD license.
43066  * See license.txt in the OpenLayers distribution or repository for the
43067  * full text of the license. */
43068
43069 /**
43070  * @requires OpenLayers/Strategy.js
43071  * @requires OpenLayers/Filter.js
43072  */
43073
43074 /**
43075  * Class: OpenLayers.Strategy.Filter
43076  * Strategy for limiting features that get added to a layer by 
43077  *     evaluating a filter.  The strategy maintains a cache of
43078  *     all features until removeFeatures is called on the layer.
43079  *
43080  * Inherits from:
43081  *  - <OpenLayers.Strategy>
43082  */
43083 OpenLayers.Strategy.Filter = OpenLayers.Class(OpenLayers.Strategy, {
43084     
43085     /**
43086      * APIProperty: filter
43087      * {<OpenLayers.Filter>}  Filter for limiting features sent to the layer.
43088      *     Use the <setFilter> method to update this filter after construction.
43089      */
43090     filter: null,
43091     
43092     /**
43093      * Property: cache
43094      * {Array(<OpenLayers.Feature.Vector>)} List of currently cached
43095      *     features.
43096      */
43097     cache: null,
43098     
43099     /**
43100      * Property: caching
43101      * {Boolean} The filter is currently caching features.
43102      */
43103     caching: false,
43104     
43105     /**
43106      * Constructor: OpenLayers.Strategy.Filter
43107      * Create a new filter strategy.
43108      *
43109      * Parameters:
43110      * options - {Object} Optional object whose properties will be set on the
43111      *     instance.
43112      */
43113
43114     /**
43115      * APIMethod: activate
43116      * Activate the strategy.  Register any listeners, do appropriate setup.
43117      *     By default, this strategy automatically activates itself when a layer
43118      *     is added to a map.
43119      *
43120      * Returns:
43121      * {Boolean} True if the strategy was successfully activated or false if
43122      *      the strategy was already active.
43123      */
43124     activate: function() {
43125         var activated = OpenLayers.Strategy.prototype.activate.apply(this, arguments);
43126         if (activated) {
43127             this.cache = [];
43128             this.layer.events.on({
43129                 "beforefeaturesadded": this.handleAdd,
43130                 "beforefeaturesremoved": this.handleRemove,
43131                 scope: this
43132             });
43133         }
43134         return activated;
43135     },
43136     
43137     /**
43138      * APIMethod: deactivate
43139      * Deactivate the strategy.  Clear the feature cache.
43140      *
43141      * Returns:
43142      * {Boolean} True if the strategy was successfully deactivated or false if
43143      *      the strategy was already inactive.
43144      */
43145     deactivate: function() {
43146         this.cache = null;
43147         if (this.layer && this.layer.events) {
43148             this.layer.events.un({
43149                 "beforefeaturesadded": this.handleAdd,
43150                 "beforefeaturesremoved": this.handleRemove,
43151                 scope: this
43152             });            
43153         }
43154         return OpenLayers.Strategy.prototype.deactivate.apply(this, arguments);
43155     },
43156     
43157     /**
43158      * Method: handleAdd
43159      */
43160     handleAdd: function(event) {
43161         if (!this.caching && this.filter) {
43162             var features = event.features;
43163             event.features = [];
43164             var feature;
43165             for (var i=0, ii=features.length; i<ii; ++i) {
43166                 feature = features[i];
43167                 if (this.filter.evaluate(feature)) {
43168                     event.features.push(feature);
43169                 } else {
43170                     this.cache.push(feature);
43171                 }
43172             }
43173         }
43174     },
43175     
43176     /**
43177      * Method: handleRemove
43178      */
43179     handleRemove: function(event) {
43180         if (!this.caching) {
43181             this.cache = [];
43182         }
43183     },
43184
43185     /** 
43186      * APIMethod: setFilter
43187      * Update the filter for this strategy.  This will re-evaluate
43188      *     any features on the layer and in the cache.  Only features
43189      *     for which filter.evalute(feature) returns true will be
43190      *     added to the layer.  Others will be cached by the strategy.
43191      *
43192      * Parameters:
43193      * filter - {<OpenLayers.Filter>} A filter for evaluating features.
43194      */
43195     setFilter: function(filter) {
43196         this.filter = filter;
43197         var previousCache = this.cache;
43198         this.cache = [];
43199         // look through layer for features to remove from layer
43200         this.handleAdd({features: this.layer.features});
43201         // cache now contains features to remove from layer
43202         if (this.cache.length > 0) {
43203             this.caching = true;
43204             this.layer.removeFeatures(this.cache.slice());
43205             this.caching = false;
43206         }
43207         // now look through previous cache for features to add to layer
43208         if (previousCache.length > 0) {
43209             var event = {features: previousCache};
43210             this.handleAdd(event);
43211             if (event.features.length > 0) {
43212                 // event has features to add to layer
43213                 this.caching = true;
43214                 this.layer.addFeatures(event.features);
43215                 this.caching = false;
43216             }
43217         }
43218     },
43219
43220     CLASS_NAME: "OpenLayers.Strategy.Filter"
43221
43222 });
43223 /* ======================================================================
43224     OpenLayers/Strategy/BBOX.js
43225    ====================================================================== */
43226
43227 /* Copyright (c) 2006-2015 by OpenLayers Contributors (see authors.txt for
43228  * full list of contributors). Published under the 2-clause BSD license.
43229  * See license.txt in the OpenLayers distribution or repository for the
43230  * full text of the license. */
43231
43232 /**
43233  * @requires OpenLayers/Strategy.js
43234  * @requires OpenLayers/Filter/Spatial.js
43235  */
43236
43237 /**
43238  * Class: OpenLayers.Strategy.BBOX
43239  * A simple strategy that reads new features when the viewport invalidates
43240  *     some bounds.
43241  *
43242  * Inherits from:
43243  *  - <OpenLayers.Strategy>
43244  */
43245 OpenLayers.Strategy.BBOX = OpenLayers.Class(OpenLayers.Strategy, {
43246     
43247     /**
43248      * Property: bounds
43249      * {<OpenLayers.Bounds>} The current data bounds (in the same projection
43250      *     as the layer - not always the same projection as the map).
43251      */
43252     bounds: null,
43253     
43254     /** 
43255      * Property: resolution 
43256      * {Float} The current data resolution. 
43257      */ 
43258     resolution: null, 
43259            
43260     /**
43261      * APIProperty: ratio
43262      * {Float} The ratio of the data bounds to the viewport bounds (in each
43263      *     dimension).  Default is 2.
43264      */
43265     ratio: 2,
43266
43267     /** 
43268      * Property: resFactor 
43269      * {Float} Optional factor used to determine when previously requested 
43270      *     features are invalid.  If set, the resFactor will be compared to the
43271      *     resolution of the previous request to the current map resolution.
43272      *     If resFactor > (old / new) and 1/resFactor < (old / new).  If you
43273      *     set a resFactor of 1, data will be requested every time the
43274      *     resolution changes.  If you set a resFactor of 3, data will be
43275      *     requested if the old resolution is 3 times the new, or if the new is
43276      *     3 times the old.  If the old bounds do not contain the new bounds
43277      *     new data will always be requested (with or without considering
43278      *     resFactor). 
43279      */ 
43280     resFactor: null, 
43281     
43282     /**
43283      * Property: response
43284      * {<OpenLayers.Protocol.Response>} The protocol response object returned
43285      *      by the layer protocol.
43286      */
43287     response: null,
43288
43289     /**
43290      * Constructor: OpenLayers.Strategy.BBOX
43291      * Create a new BBOX strategy.
43292      *
43293      * Parameters:
43294      * options - {Object} Optional object whose properties will be set on the
43295      *     instance.
43296      */
43297     
43298     /**
43299      * Method: activate
43300      * Set up strategy with regard to reading new batches of remote data.
43301      * 
43302      * Returns:
43303      * {Boolean} The strategy was successfully activated.
43304      */
43305     activate: function() {
43306         var activated = OpenLayers.Strategy.prototype.activate.call(this);
43307         if(activated) {
43308             this.layer.events.on({
43309                 "moveend": this.update,
43310                 "refresh": this.update,
43311                 "visibilitychanged": this.update,
43312                 scope: this
43313             });
43314             this.update();
43315         }
43316         return activated;
43317     },
43318     
43319     /**
43320      * Method: deactivate
43321      * Tear down strategy with regard to reading new batches of remote data.
43322      * 
43323      * Returns:
43324      * {Boolean} The strategy was successfully deactivated.
43325      */
43326     deactivate: function() {
43327         var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
43328         if(deactivated) {
43329             this.layer.events.un({
43330                 "moveend": this.update,
43331                 "refresh": this.update,
43332                 "visibilitychanged": this.update,
43333                 scope: this
43334             });
43335         }
43336         return deactivated;
43337     },
43338
43339     /**
43340      * Method: update
43341      * Callback function called on "moveend" or "refresh" layer events.
43342      *
43343      * Parameters:
43344      * options - {Object} Optional object whose properties will determine
43345      *     the behaviour of this Strategy
43346      *
43347      * Valid options include:
43348      * force - {Boolean} if true, new data must be unconditionally read.
43349      * noAbort - {Boolean} if true, do not abort previous requests.
43350      */
43351     update: function(options) {
43352         var mapBounds = this.getMapBounds();
43353         if (mapBounds !== null && ((options && options.force) ||
43354           (this.layer.visibility && this.layer.calculateInRange() && this.invalidBounds(mapBounds)))) {
43355             this.calculateBounds(mapBounds);
43356             this.resolution = this.layer.map.getResolution(); 
43357             this.triggerRead(options);
43358         }
43359     },
43360     
43361     /**
43362      * Method: getMapBounds
43363      * Get the map bounds expressed in the same projection as this layer.
43364      *
43365      * Returns:
43366      * {<OpenLayers.Bounds>} Map bounds in the projection of the layer.
43367      */
43368     getMapBounds: function() {
43369         if (this.layer.map === null) {
43370             return null;
43371         }
43372         var bounds = this.layer.map.getExtent();
43373         if (bounds && this.layer.projection && !this.layer.projection.equals(
43374                 this.layer.map.getProjectionObject())) {
43375             bounds = bounds.clone().transform(
43376                 this.layer.map.getProjectionObject(), this.layer.projection
43377             );
43378         }
43379         return bounds;
43380     },
43381
43382     /**
43383      * Method: invalidBounds
43384      * Determine whether the previously requested set of features is invalid. 
43385      *     This occurs when the new map bounds do not contain the previously 
43386      *     requested bounds.  In addition, if <resFactor> is set, it will be 
43387      *     considered.
43388      *
43389      * Parameters:
43390      * mapBounds - {<OpenLayers.Bounds>} the current map extent, will be
43391      *      retrieved from the map object if not provided
43392      *
43393      * Returns:
43394      * {Boolean} 
43395      */
43396     invalidBounds: function(mapBounds) {
43397         if(!mapBounds) {
43398             mapBounds = this.getMapBounds();
43399         }
43400         var invalid = !this.bounds || !this.bounds.containsBounds(mapBounds);
43401         if(!invalid && this.resFactor) {
43402             var ratio = this.resolution / this.layer.map.getResolution();
43403             invalid = (ratio >= this.resFactor || ratio <= (1 / this.resFactor));
43404         }
43405         return invalid;
43406     },
43407  
43408     /**
43409      * Method: calculateBounds
43410      *
43411      * Parameters:
43412      * mapBounds - {<OpenLayers.Bounds>} the current map extent, will be
43413      *      retrieved from the map object if not provided
43414      */
43415     calculateBounds: function(mapBounds) {
43416         if(!mapBounds) {
43417             mapBounds = this.getMapBounds();
43418         }
43419         var center = mapBounds.getCenterLonLat();
43420         var dataWidth = mapBounds.getWidth() * this.ratio;
43421         var dataHeight = mapBounds.getHeight() * this.ratio;
43422         this.bounds = new OpenLayers.Bounds(
43423             center.lon - (dataWidth / 2),
43424             center.lat - (dataHeight / 2),
43425             center.lon + (dataWidth / 2),
43426             center.lat + (dataHeight / 2)
43427         );
43428     },
43429     
43430     /**
43431      * Method: triggerRead
43432      *
43433      * Parameters:
43434      * options - {Object} Additional options for the protocol's read method 
43435      *     (optional)
43436      *
43437      * Returns:
43438      * {<OpenLayers.Protocol.Response>} The protocol response object
43439      *      returned by the layer protocol.
43440      */
43441     triggerRead: function(options) {
43442         if (this.response && !(options && options.noAbort === true)) {
43443             this.layer.protocol.abort(this.response);
43444             this.layer.events.triggerEvent("loadend");
43445         }
43446         var evt = {filter: this.createFilter()};
43447         this.layer.events.triggerEvent("loadstart", evt);
43448         this.response = this.layer.protocol.read(
43449             OpenLayers.Util.applyDefaults({
43450                 filter: evt.filter,
43451                 callback: this.merge,
43452                 scope: this
43453         }, options));
43454     },
43455  
43456     /**
43457      * Method: createFilter
43458      * Creates a spatial BBOX filter. If the layer that this strategy belongs
43459      * to has a filter property, this filter will be combined with the BBOX 
43460      * filter.
43461      * 
43462      * Returns
43463      * {<OpenLayers.Filter>} The filter object.
43464      */
43465     createFilter: function() {
43466         var filter = new OpenLayers.Filter.Spatial({
43467             type: OpenLayers.Filter.Spatial.BBOX,
43468             value: this.bounds,
43469             projection: this.layer.projection
43470         });
43471         if (this.layer.filter) {
43472             filter = new OpenLayers.Filter.Logical({
43473                 type: OpenLayers.Filter.Logical.AND,
43474                 filters: [this.layer.filter, filter]
43475             });
43476         }
43477         return filter;
43478     },
43479    
43480     /**
43481      * Method: merge
43482      * Given a list of features, determine which ones to add to the layer.
43483      *     If the layer projection differs from the map projection, features
43484      *     will be transformed from the layer projection to the map projection.
43485      *
43486      * Parameters:
43487      * resp - {<OpenLayers.Protocol.Response>} The response object passed
43488      *      by the protocol.
43489      */
43490     merge: function(resp) {
43491         this.layer.destroyFeatures();
43492         if (resp.success()) {
43493             var features = resp.features;
43494             if(features && features.length > 0) {
43495                 var remote = this.layer.projection;
43496                 var local = this.layer.map.getProjectionObject();
43497                 if(remote && local && !local.equals(remote)) {
43498                     var geom;
43499                     for(var i=0, len=features.length; i<len; ++i) {
43500                         geom = features[i].geometry;
43501                         if(geom) {
43502                             geom.transform(remote, local);
43503                         }
43504                     }
43505                 }
43506                 this.layer.addFeatures(features);
43507             }
43508         } else {
43509             this.bounds = null;
43510         }
43511         this.response = null;
43512         this.layer.events.triggerEvent("loadend", {response: resp});
43513     },
43514    
43515     CLASS_NAME: "OpenLayers.Strategy.BBOX" 
43516 });